Merge pull request #87 from papauorg/feature/development-environment-upstream

Provide preconfigured dev-environment and automated tests -> some fixes still required for femrneter recipes and config path
This commit is contained in:
Alexander Vollkopf 2022-04-01 18:40:26 +02:00 committed by GitHub
commit 46a589c463
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
75 changed files with 1445 additions and 988 deletions

20
.devcontainer/Dockerfile Normal file
View file

@ -0,0 +1,20 @@
FROM mcr.microsoft.com/vscode/devcontainers/python:3.9-bullseye
RUN apt-get update \
&& apt-get upgrade -y
RUN apt-get install --no-install-recommends -y \
libatlas-base-dev \
libffi-dev \
python3-pip \
&& rm -rf /var/lib/apt/lists/*
RUN python3 -m pip install --no-cache-dir --upgrade pip setuptools wheel
# Install craftbeerpi requirements for better caching
COPY ./requirements.txt /workspace/requirements.txt
RUN pip3 install --no-cache-dir -r /workspace/requirements.txt
# Install current version of cbpi-ui
RUN mkdir /opt/downloads \
&& curl https://github.com/craftbeerpi/craftbeerpi4-ui/archive/main.zip -L -o /opt/downloads/cbpi-ui.zip \
&& pip3 install --no-cache-dir /opt/downloads/cbpi-ui.zip \
&& rm -rf /opt/downloads

View file

@ -0,0 +1,3 @@
{
"data": []
}

View file

@ -0,0 +1,6 @@
[Desktop Entry]
Type=Application
Name=Chromium
Comment=Chromium Webbrowser
NoDisplay=false
Exec=chromium-browser --noerrordialogs --disable-session-crashed-bubble --disable-infobars --force-device-scale-factor=1.00 --start-fullscreen "http://localhost:8000"

View file

@ -0,0 +1,335 @@
{
"AUTHOR": {
"description": "Author",
"name": "AUTHOR",
"options": null,
"type": "string",
"value": "John Doe"
},
"AddMashInStep": {
"description": "Add MashIn Step automatically if not defined in recipe",
"name": "AddMashInStep",
"options": [
{
"label": "Yes",
"value": "Yes"
},
{
"label": "No",
"value": "No"
}
],
"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": "CraftBeerPi Brewery"
},
"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": ""
},
"CBPI_TEST_3": {
"description": "test",
"name": "CBPI_TEST_3",
"options": null,
"type": "string",
"value": "1"
},
"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
},
"RECIPE_CREATION_PATH": {
"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": "upload"
},
"TEMP_UNIT": {
"description": "Temperature Unit",
"name": "TEMP_UNIT",
"options": [
{
"label": "C",
"value": "C"
},
{
"label": "F",
"value": "F"
}
],
"type": "select",
"value": "C"
},
"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
},
"steps_boil": {
"description": "Boil step type",
"name": "steps_boil",
"options": null,
"type": "step",
"value": "BoilStep"
},
"steps_boil_temp": {
"description": "Default Boil Temperature for Recipe Creation",
"name": "steps_boil_temp",
"options": null,
"type": "number",
"value": "99"
},
"steps_cooldown": {
"description": "Cooldown step type",
"name": "steps_cooldown",
"options": null,
"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",
"options": null,
"type": "sensor",
"value": ""
},
"steps_cooldown_temp": {
"description": "Cooldown temp will send notification when this temeprature is reached",
"name": "steps_cooldown_temp",
"options": null,
"type": "number",
"value": "20"
},
"steps_mash": {
"description": "Mash step type",
"name": "steps_mash",
"options": null,
"type": "step",
"value": "MashStep"
},
"steps_mashin": {
"description": "MashIn step type",
"name": "steps_mashin",
"options": null,
"type": "step",
"value": "MashInStep"
},
"steps_mashout": {
"description": "MashOut step type",
"name": "steps_mashout",
"options": null,
"type": "step",
"value": "NotificationStep"
}
}

View file

@ -0,0 +1,20 @@
name: CraftBeerPi
version: 4.0.8
index_url: /cbpi_ui/static/index.html
port: 8000
mqtt: true
mqtt_host: mqtt
mqtt_port: 1883
mqtt_username: craftbeerpi
mqtt_password: cbpiSuperSecMq77!
username: cbpi
password: 123
plugins:
- cbpi4ui

View file

@ -0,0 +1,9 @@
[Unit]
Description=Craftbeer Pi
[Service]
WorkingDirectory=/home/pi
ExecStart=/usr/local/bin/cbpi start
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,3 @@
{
"elements": []
}

View file

@ -0,0 +1,3 @@
{
"data": []
}

View file

@ -0,0 +1,3 @@
{
"data": []
}

View file

@ -0,0 +1,3 @@
{
"data": []
}

View file

@ -0,0 +1,6 @@
{
"basic": {
"name": ""
},
"steps": []
}

View file

@ -0,0 +1,5 @@
#!/bin/bash
USER=craftbeerpi
PASSWORD=craftbeerpi
docker run -it --rm -v "$(pwd)/mosquitto/config/mosquitto.passwd:/opt/passwdfile" eclipse-mosquitto:2 mosquitto_passwd -b /opt/passwdfile $USER $PASSWORD

View file

@ -0,0 +1,51 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.177.0/containers/docker-existing-docker-compose
// If you want to run as a non-root user in the container, see .devcontainer/docker-compose.yml.
{
"name": "CraftBeerPi4",
// Update the 'dockerComposeFile' list if you have more compose files or use different names.
// The .devcontainer/docker-compose.yml file contains any overrides you need/want to make.
"dockerComposeFile": [
"docker-compose.dev.yml"
],
// The 'service' property is the name of the service for the container that VS Code should
// use. Update this value and .devcontainer/docker-compose.yml to the real service name.
"service": "craftbeerpi4-development",
// The optional 'workspaceFolder' property is the path VS Code should open by default when
// connected. This is typically a file mount in .devcontainer/docker-compose.yml
"workspaceFolder": "/workspace",
// Set *default* container specific settings.json values on container create.
"settings": {
//"terminal.integrated.shell.linux": null
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"ms-python.python",
"ms-azuretools.vscode-docker",
"editorconfig.editorconfig",
"eamodio.gitlens"
],
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [
"craftbeerpi4-development:8000",
"mqtt-explorer:4000"
],
// Uncomment the next line if you want start specific services in your Docker Compose config.
// "runServices": [],
// Uncomment the next line if you want to keep your containers running after VS Code shuts down.
"shutdownAction": "stopCompose",
// Uncomment the next line to run commands after the container is created - for example installing curl.
//"postCreateCommand": "pip3 install -r ./requirements.txt",
// Uncomment to connect as a non-root user if you've added one. See https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode"
}

View file

@ -0,0 +1,30 @@
version: '3.4'
services:
mqtt:
image: eclipse-mosquitto:2
volumes:
- "./mosquitto/config:/mosquitto/config"
restart: unless-stopped
craftbeerpi4-development:
build:
context: ../
dockerfile: .devcontainer/Dockerfile
command: /bin/sh -c "while sleep 1000; do :; done"
user: vscode
depends_on:
- mqtt
volumes:
# Update this to wherever you want VS Code to mount the folder of your project
- ../:/workspace:cached
mqtt-explorer:
image: smeagolworms4/mqtt-explorer
environment:
HTTP_PORT: 4000
CONFIG_PATH: /mqtt-explorer/config
volumes:
- "./mqtt-explorer/config:/mqtt-explorer/config"
depends_on:
- mqtt
restart: unless-stopped

View file

@ -0,0 +1,10 @@
persistence true
persistence_location /mosquitto/data
log_dest file /mosquitto/log/mosquitto.log
log_dest stdout
password_file /mosquitto/config/mosquitto.passwd
allow_anonymous false
port 1883

View file

@ -0,0 +1,2 @@
craftbeerpi:$7$101$cRIEIwJ9L/+TAFF1$lxT+v9SisokWaRBgB/Scut7DaotH4RMgzHttYHhwuy6m5yatSoac7bwrkztoQ7raNehBhKt/A4VVejnzozdxXA==
mqtt-explorer:$7$101$SFFKvbIBVXFFAIBp$Pgue6DaAfcuhegjEqtTjf+WWgNZ8geiv1/3fXqmJ0APmd0L80wNTSrEhnFdJmHvi0/vW6V9bVKPJfVRDIjPxCw==

View file

@ -0,0 +1,31 @@
{
"ConnectionManager_connections": {
"mqtt.eclipse.org": {
"configVersion": 1,
"certValidation": true,
"clientId": "mqtt-explorer-8eb042b9",
"id": "mqtt.eclipse.org",
"name": "CraftBeerPi MQTT Explorer",
"encryption": false,
"subscriptions": [
{
"topic": "#",
"qos": 0
},
{
"topic": "$SYS/#",
"qos": 0
}
],
"type": "mqtt",
"host": "mqtt",
"port": 1883,
"protocol": "mqtt",
"changeSet": {
"password": "mqtt-explorer"
},
"username": "mqtt-explorer",
"password": "mqtt-explorer"
}
}
}

View file

@ -27,8 +27,11 @@ jobs:
- name: Clean
run: python setup.py clean --all
# - name: Run tests
# run: python -m unittest tests
- name: Install Requirements
run: pip3 install -r requirements.txt
- name: Run tests
run: coverage run --source cbpi -m pytest tests
- name: Build source distribution package for CraftBeerPi
run: python setup.py sdist

2
.gitignore vendored
View file

@ -14,6 +14,6 @@ node_modules
.vscode
.venv*
.DS_Store
.vscode/
config/*
logs/
.coverage

16
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,16 @@
{
// Verwendet IntelliSense zum Ermitteln möglicher Attribute.
// Zeigen Sie auf vorhandene Attribute, um die zugehörigen Beschreibungen anzuzeigen.
// Weitere Informationen finden Sie unter https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Run CraftBeerPi4",
"type": "python",
"request": "launch",
"module": "run",
"args": ["--config-folder-path=./.devcontainer/cbpi-dev-config", "start"]
}
]
}

8
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,8 @@
{
"python.pythonPath": "/bin/python3",
"python.testing.pytestArgs": [
"tests"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}

View file

@ -21,7 +21,19 @@ in the documentation, that can be found here: [gitbook.io](https://openbrewing.g
Plugins extend the base functionality of CraftBeerPi 4.
You can find a list of available plugins [here](https://openbrewing.gitbook.io/craftbeerpi4_support/master/plugin-installation#plugin-list).
## 🧑‍🤝‍🧑 Contributers
## 🧑‍🤝‍🧑 Contribute
You want to help develop CraftBeerPi4? To get you quickly stated, this repository comes with a preconfigured
development environment. To be able to use this environment you need 2 things installed on your computer:
- docker
- visual studio code (vscode)
To start developing clone this repository, open the folder in vscode and use the _development container_ feature. The command is called _Reopen in container_. Please note that this quick start setup does not work if you want to develop directly on a 32bit raspberry pi os because docker is only available for 64bit arm plattform. Please use the regular development setup for that.
For a more detailed description of a development setup without the _development container_ feature see the documentation page:
[gitbook.io](https://openbrewing.gitbook.io/craftbeerpi4_support/)
### Contributors
Thanks to all the people who have contributed
[![contributors](https://contributors-img.web.app/image?repo=craftbeerpi/craftbeerpi4)](https://github.com/craftbeerpi/craftbeerpi4/graphs/contributors)

View file

@ -1,103 +1,45 @@
import argparse
import datetime
import logging
import subprocess
import sys
import re
import requests
import yaml
from cbpi.configFolder import ConfigFolder
from cbpi.utils.utils import load_config
from zipfile import ZipFile
from cbpi.craftbeerpi import CraftBeerPi
import os
import platform
import pathlib
import pkgutil
import shutil
import yaml
import click
from subprocess import call
import zipfile
from colorama import Fore, Back, Style
from importlib import import_module
import importlib
from jinja2 import Template
from importlib_metadata import metadata, version
from importlib_metadata import metadata
from tabulate import tabulate
from PyInquirer import prompt, print_json
def create_config_file():
if os.path.exists(os.path.join(".", 'config', "config.yaml")) is False:
srcfile = os.path.join(os.path.dirname(__file__), "config", "config.yaml")
destfile = os.path.join(".", 'config')
shutil.copy(srcfile, destfile)
if os.path.exists(os.path.join(".", 'config', "actor.json")) is False:
srcfile = os.path.join(os.path.dirname(__file__), "config", "actor.json")
destfile = os.path.join(".", 'config')
shutil.copy(srcfile, destfile)
class CraftBeerPiCli():
def __init__(self, config) -> None:
self.config = config
pass
if os.path.exists(os.path.join(".", 'config', "sensor.json")) is False:
srcfile = os.path.join(os.path.dirname(__file__), "config", "sensor.json")
destfile = os.path.join(".", 'config')
shutil.copy(srcfile, destfile)
def setup(self):
print("Setting up CraftBeerPi")
self.config.create_home_folder_structure()
self.config.create_config_file()
if os.path.exists(os.path.join(".", 'config', "kettle.json")) is False:
srcfile = os.path.join(os.path.dirname(__file__), "config", "kettle.json")
destfile = os.path.join(".", 'config')
shutil.copy(srcfile, destfile)
def start(self):
if self.config.check_for_setup() is False:
return
print("START")
cbpi = CraftBeerPi(self.config)
cbpi.start()
if os.path.exists(os.path.join(".", 'config', "fermenter_data.json")) is False:
srcfile = os.path.join(os.path.dirname(__file__), "config", "fermenter_data.json")
destfile = os.path.join(".", 'config')
shutil.copy(srcfile, destfile)
if os.path.exists(os.path.join(".", 'config', "step_data.json")) is False:
srcfile = os.path.join(os.path.dirname(__file__), "config", "step_data.json")
destfile = os.path.join(".", 'config')
shutil.copy(srcfile, destfile)
if os.path.exists(os.path.join(".", 'config', "config.json")) is False:
srcfile = os.path.join(os.path.dirname(__file__), "config", "config.json")
destfile = os.path.join(".", 'config')
shutil.copy(srcfile, destfile)
if os.path.exists(os.path.join(".", 'config', "dashboard", "cbpi_dashboard_1.json")) is False:
srcfile = os.path.join(os.path.dirname(__file__), "config", "dashboard", "cbpi_dashboard_1.json")
destfile = os.path.join(".", "config", "dashboard")
shutil.copy(srcfile, destfile)
if os.path.exists(os.path.join(".", 'config', "carftbeerpi.service")) is False:
srcfile = os.path.join(os.path.dirname(__file__), "config", "craftbeerpi.service")
destfile = os.path.join(".", 'config')
shutil.copy(srcfile, destfile)
if os.path.exists(os.path.join(".", 'config', "chromium.desktop")) is False:
srcfile = os.path.join(os.path.dirname(__file__), "config", "chromium.desktop")
destfile = os.path.join(".", 'config')
shutil.copy(srcfile, destfile)
print("Config Folder created")
def create_home_folder_structure():
pathlib.Path(os.path.join(".", 'logs/sensors')).mkdir(parents=True, exist_ok=True)
pathlib.Path(os.path.join(".", 'config')).mkdir(parents=True, exist_ok=True)
pathlib.Path(os.path.join(".", 'config/dashboard')).mkdir(parents=True, exist_ok=True)
pathlib.Path(os.path.join(".", 'config/dashboard/widgets')).mkdir(parents=True, exist_ok=True)
pathlib.Path(os.path.join(".", 'config/recipes')).mkdir(parents=True, exist_ok=True)
pathlib.Path(os.path.join(".", 'config/fermenterrecipes')).mkdir(parents=True, exist_ok=True)
pathlib.Path(os.path.join(".", 'config/upload')).mkdir(parents=True, exist_ok=True)
print("Folder created")
def setup_one_wire():
def setup_one_wire(self):
print("Setting up 1Wire")
with open('/boot/config.txt', 'w') as f:
f.write("dtoverlay=w1-gpio,gpiopin=4,pullup=on")
print("/boot/config.txt created")
def list_one_wire():
def list_one_wire(self):
print("List 1Wire")
call(["modprobe", "w1-gpio"])
call(["modprobe", "w1-therm"])
@ -108,88 +50,7 @@ def list_one_wire():
except Exception as e:
print(e)
def copy_splash():
srcfile = os.path.join(".", "config", "splash.png")
destfile = os.path.join(".", 'config')
shutil.copy(srcfile, destfile)
print("Splash Srceen created")
def clear_db():
import os.path
if os.path.exists(os.path.join(".", "craftbeerpi.db")) is True:
os.remove(os.path.join(".", "craftbeerpi.db"))
print("database cleared")
def recursive_chown(path, owner, group):
for dirpath, dirnames, filenames in os.walk(path):
shutil.chown(dirpath, owner, group)
for filename in filenames:
shutil.chown(os.path.join(dirpath, filename), owner, group)
def check_for_setup():
if os.path.exists(os.path.join(".", "config", "config.yaml")) is False:
print("***************************************************")
print("CraftBeerPi Config File not found: %s" % os.path.join(".", "config", "config.yaml"))
print("Please run 'cbpi setup' before starting the server ")
print("***************************************************")
return False
if os.path.exists(os.path.join(".", "config", "upload")) is False:
print("***************************************************")
print("CraftBeerPi upload folder not found: %s" % os.path.join(".", "config/upload"))
print("Please run 'cbpi setup' before starting the server ")
print("***************************************************")
return False
# if os.path.exists(os.path.join(".", "config", "fermenterrecipes")) is False:
# print("***************************************************")
# print("CraftBeerPi fermenterrecipes folder not found: %s" % os.path.join(".", "config/fermenterrecipes"))
# print("Please run 'cbpi setup' before starting the server ")
# print("***************************************************")
# return False
backupfile = os.path.join(".", "restored_config.zip")
if os.path.exists(os.path.join(backupfile)) is True:
print("***************************************************")
print("Found backup of config. Starting restore")
required_content=['dashboard/', 'recipes/', 'upload/', 'config.json', 'config.yaml']
zip=zipfile.ZipFile(backupfile)
zip_content_list = zip.namelist()
zip_content = True
print("Checking content of zip file")
for content in required_content:
try:
check = zip_content_list.index(content)
except:
zip_content = False
if zip_content == True:
print("Found correct content. Starting Restore process")
output_path = pathlib.Path(os.path.join(".", 'config'))
system = platform.system()
print(system)
if system != "Windows":
owner = output_path.owner()
group = output_path.group()
print("Removing old config folder")
shutil.rmtree(output_path, ignore_errors=True)
print("Extracting zip file to config folder")
zip.extractall(output_path)
zip.close()
if system != "Windows":
print(f"Changing owner and group of config folder recursively to {owner}:{group}")
recursive_chown(output_path, owner, group)
print("Removing backup file")
os.remove(backupfile)
else:
print("Wrong Content in zip file. No restore possible")
print("Removing zip file")
os.remove(backupfile)
print("***************************************************")
return True
else:
return True
def plugins_list():
def plugins_list(self):
result = []
print("")
print(Fore.LIGHTYELLOW_EX,"List of active plugins", Style.RESET_ALL)
@ -201,7 +62,6 @@ def plugins_list():
if name.startswith('cbpi') and len(name) > 4
}
for key, module in discovered_plugins.items():
try:
meta = metadata(key)
result.append(dict(Name=meta["Name"], Version=meta["Version"], Author=meta["Author"], Homepage=meta["Home-page"], Summary=meta["Summary"]))
@ -211,9 +71,7 @@ def plugins_list():
print(Fore.LIGHTGREEN_EX, tabulate(result, headers="keys"), Style.RESET_ALL)
def plugin_create():
def plugin_create(self):
print("Plugin Creation")
print("")
@ -271,7 +129,6 @@ def plugin_create():
with open(os.path.join(".", name, name, "config.yaml"), "w") as fh:
fh.write(outputText)
print("")
print("")
print("Plugin {}{}{} created! ".format(Fore.LIGHTGREEN_EX, name, Style.RESET_ALL) )
@ -282,66 +139,8 @@ def plugin_create():
print("")
print("")
@click.group()
def main():
print("---------------------")
print("Welcome to CBPi")
print("---------------------")
level = logging.INFO
logging.basicConfig(level=level, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
pass
@click.command()
def setup():
'''Create Config folder'''
print("Setting up CraftBeerPi")
create_home_folder_structure()
create_config_file()
@click.command()
@click.option('--list', is_flag=True, help="List all 1Wire Devices")
@click.option('--setup', is_flag=True, help="Setup 1Wire on Raspberry Pi")
def onewire(list, setup):
'''Setup 1wire on Raspberry Pi'''
if setup is True:
setup_one_wire()
if list is True:
list_one_wire()
@click.command()
def start():
'''Lets go brewing'''
if check_for_setup() is False:
return
print("Starting up CraftBeerPi ...")
cbpi = CraftBeerPi()
cbpi.start()
@click.command()
def plugins():
'''List active plugins'''
plugins_list()
return
@click.command()
def create():
'''Create New Plugin'''
plugin_create()
@click.command()
@click.argument('name')
def autostart(name):
'''(on|off|status) Enable or disable autostart'''
def autostart(self, name):
'''Enable or disable autostart'''
if(name == "status"):
if os.path.exists(os.path.join("/etc/systemd/system","craftbeerpi.service")) is True:
print("CraftBeerPi Autostart is {}ON{}".format(Fore.LIGHTGREEN_EX,Style.RESET_ALL))
@ -351,7 +150,7 @@ def autostart(name):
print("Add craftbeerpi.service to systemd")
try:
if os.path.exists(os.path.join("/etc/systemd/system","craftbeerpi.service")) is False:
srcfile = os.path.join(".", "config", "craftbeerpi.service")
srcfile = self.config.get_file_path("craftbeerpi.service")
destfile = os.path.join("/etc/systemd/system")
shutil.copy(srcfile, destfile)
print("Copied craftbeerpi.service to /etc/systemd/system")
@ -388,12 +187,8 @@ def autostart(name):
return
@click.command()
@click.argument('name')
def chromium(name):
'''(on|off|status) Enable or disable Kiosk mode'''
def chromium(self, name):
'''Enable or disable autostart'''
if(name == "status"):
if os.path.exists(os.path.join("/etc/xdg/autostart/","chromium.desktop")) is True:
print("CraftBeerPi Chromium Desktop is {}ON{}".format(Fore.LIGHTGREEN_EX,Style.RESET_ALL))
@ -403,7 +198,7 @@ def chromium(name):
print("Add chromium.desktop to /etc/xdg/autostart/")
try:
if os.path.exists(os.path.join("/etc/xdg/autostart/","chromium.desktop")) is False:
srcfile = os.path.join(".", "config", "chromium.desktop")
srcfile = self.config.get_file_path("chromium.desktop")
destfile = os.path.join("/etc/xdg/autostart/")
shutil.copy(srcfile, destfile)
print("Copied chromium.desktop to /etc/xdg/autostart/")
@ -426,10 +221,65 @@ def chromium(name):
return
return
main.add_command(setup)
main.add_command(start)
main.add_command(autostart)
main.add_command(chromium)
main.add_command(plugins)
main.add_command(onewire)
main.add_command(create)
@click.group()
@click.pass_context
@click.option('--config-folder-path', '-c', default="./config", type=click.Path(), help="Specify where the config folder is located. Defaults to './config'.")
def main(context, config_folder_path):
print("---------------------")
print("Welcome to CBPi")
print("---------------------")
level = logging.INFO
logging.basicConfig(level=level, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
cbpi_cli = CraftBeerPiCli(ConfigFolder(config_folder_path))
context.obj = cbpi_cli
pass
@main.command()
@click.pass_context
def setup(context):
'''Create Config folder'''
context.obj.setup()
@main.command()
@click.pass_context
@click.option('--list', is_flag=True, help="List all 1Wire Devices")
@click.option('--setup', is_flag=True, help="Setup 1Wire on Raspberry Pi")
def onewire(context, list, setup):
'''Setup 1wire on Raspberry Pi'''
if setup is True:
context.obj.setup_one_wire()
if list is True:
context.obj.list_one_wire()
@main.command()
@click.pass_context
def start(context):
context.obj.start()
@main.command()
@click.pass_context
def plugins(context):
'''List active plugins'''
context.obj.plugins_list()
@click.command()
@click.pass_context
def create(context):
'''Create New Plugin'''
context.obj.plugin_create()
@main.command()
@click.pass_context
@click.argument('name')
def autostart(context, name):
'''(on|off|status) Enable or disable autostart'''
context.obj.autostart(name)
@main.command()
@click.pass_context
@click.argument('name')
def chromium(context, name):
'''(on|off|status) Enable or disable Kiosk mode'''
context.obj.chromium(name)

130
cbpi/configFolder.py Normal file
View file

@ -0,0 +1,130 @@
import os
import pathlib
import platform
import shutil
import zipfile
import glob
class ConfigFolder:
def __init__(self, configFolderPath):
self._rawPath = configFolderPath
def config_file_exists(self, path):
return os.path.exists(self.get_file_path(path))
def get_file_path(self, file):
return os.path.join(self._rawPath, file)
def get_upload_file(self, file):
return os.path.join(self._rawPath, 'upload', file)
def get_recipe_file_by_id(self, recipe_id):
return os.path.join(self._rawPath, 'recipes', "{}.yaml".format(recipe_id))
def get_fermenter_recipe_by_id(self, recipe_id):
return os.path.join(self._rawPath, 'fermenterrecipes', "{}.yaml".format(recipe_id))
def get_all_fermenter_recipes(self):
fermenter_recipes_folder = os.path.join(self._rawPath, 'fermenterrecipes', '*.yaml')
return glob.glob(fermenter_recipes_folder)
def check_for_setup(self):
if self.config_file_exists("config.yaml") is False:
print("***************************************************")
print("CraftBeerPi Config File not found: %s" % self.get_file_path("config.yaml"))
print("Please run 'cbpi setup' before starting the server ")
print("***************************************************")
return False
if self.config_file_exists("upload") is False:
print("***************************************************")
print("CraftBeerPi upload folder not found: %s" % self.get_file_path("upload"))
print("Please run 'cbpi setup' before starting the server ")
print("***************************************************")
return False
# if os.path.exists(os.path.join(".", "config", "fermenterrecipes")) is False:
# print("***************************************************")
# print("CraftBeerPi fermenterrecipes folder not found: %s" % os.path.join(".", "config/fermenterrecipes"))
# print("Please run 'cbpi setup' before starting the server ")
# print("***************************************************")
# return False
backupfile = os.path.join(".", "restored_config.zip")
if os.path.exists(os.path.join(backupfile)) is True:
print("***************************************************")
print("Found backup of config. Starting restore")
required_content=['dashboard/', 'recipes/', 'upload/', 'config.json', 'config.yaml']
zip=zipfile.ZipFile(backupfile)
zip_content_list = zip.namelist()
zip_content = True
print("Checking content of zip file")
for content in required_content:
try:
check = zip_content_list.index(content)
except:
zip_content = False
if zip_content == True:
print("Found correct content. Starting Restore process")
output_path = pathlib.Path(self._rawPath)
system = platform.system()
print(system)
if system != "Windows":
owner = output_path.owner()
group = output_path.group()
print("Removing old config folder")
shutil.rmtree(output_path, ignore_errors=True)
print("Extracting zip file to config folder")
zip.extractall(output_path)
zip.close()
if system != "Windows":
print(f"Changing owner and group of config folder recursively to {owner}:{group}")
self.recursive_chown(output_path, owner, group)
print("Removing backup file")
os.remove(backupfile)
else:
print("Wrong Content in zip file. No restore possible")
print("Removing zip file")
os.remove(backupfile)
print("***************************************************")
def copyDefaultFileIfNotExists(self, file):
if self.config_file_exists(file) is False:
srcfile = os.path.join(os.path.dirname(__file__), "config", file)
destfile = os.path.join(self._rawPath, file)
shutil.copy(srcfile, destfile)
def create_config_file(self):
self.copyDefaultFileIfNotExists("config.yaml")
self.copyDefaultFileIfNotExists("actor.json")
self.copyDefaultFileIfNotExists("sensor.json")
self.copyDefaultFileIfNotExists("kettle.json")
self.copyDefaultFileIfNotExists("fermenter_data.json")
self.copyDefaultFileIfNotExists("step_data.json")
self.copyDefaultFileIfNotExists("config.json")
self.copyDefaultFileIfNotExists("craftbeerpi.service")
self.copyDefaultFileIfNotExists("chromium.desktop")
if os.path.exists(os.path.join(self._rawPath, "dashboard", "cbpi_dashboard_1.json")) is False:
srcfile = os.path.join(os.path.dirname(__file__), "config", "dashboard", "cbpi_dashboard_1.json")
destfile = os.path.join(self._rawPath, "dashboard")
shutil.copy(srcfile, destfile)
print("Config Folder created")
def create_home_folder_structure(configFolder):
pathlib.Path(os.path.join(".", 'logs/sensors')).mkdir(parents=True, exist_ok=True)
configFolder.create_folders()
print("Folder created")
def create_folders(self):
pathlib.Path(self._rawPath).mkdir(parents=True, exist_ok=True)
pathlib.Path(os.path.join(self._rawPath, 'dashboard', 'widgets')).mkdir(parents=True, exist_ok=True)
pathlib.Path(os.path.join(self._rawPath, 'recipes')).mkdir(parents=True, exist_ok=True)
pathlib.Path(os.path.join(self._rawPath, 'upload')).mkdir(parents=True, exist_ok=True)
def recursive_chown(path, owner, group):
for dirpath, dirnames, filenames in os.walk(path):
shutil.chown(dirpath, owner, group)
for filename in filenames:
shutil.chown(os.path.join(dirpath, filename), owner, group)

View file

@ -23,7 +23,7 @@ class BasicController:
self.data = []
self.autostart = True
#self._loop = asyncio.get_event_loop()
self.path = os.path.join(".", 'config', file)
self.path = self.cbpi.config_folder.get_file_path(file)
self.cbpi.app.on_cleanup.append(self.shutdown)
async def init(self):

View file

@ -14,8 +14,8 @@ class ConfigController:
self.logger = logging.getLogger(__name__)
self.cbpi = cbpi
self.cbpi.register(self)
self.path = os.path.join(".", 'config', "config.json")
self.path_static = os.path.join(".", 'config', "config.yaml")
self.path = cbpi.config_folder.get_file_path("config.json")
self.path_static = cbpi.config_folder.get_file_path("config.yaml")
def get_state(self):

View file

@ -18,14 +18,14 @@ class DashboardController:
self.logger = logging.getLogger(__name__)
self.cbpi.register(self)
self.path = os.path.join(".", 'config', "cbpi_dashboard_1.json")
self.path = cbpi.config_folder.get_file_path("cbpi_dashboard_1.json")
async def init(self):
pass
async def get_content(self, dashboard_id):
try:
self.path = os.path.join(".", 'config', "cbpi_dashboard_"+ str(dashboard_id) +".json")
self.path = self.cbpi.config_folder.get_file_path("cbpi_dashboard_"+ str(dashboard_id) +".json")
logging.info(self.path)
with open(self.path) as json_file:
data = json.load(json_file)
@ -35,21 +35,21 @@ class DashboardController:
async def add_content(self, dashboard_id, data):
print(data)
self.path = os.path.join(".", 'config', "cbpi_dashboard_" + str(dashboard_id)+ ".json")
self.path = self.cbpi.config_folder.get_file_path("cbpi_dashboard_" + str(dashboard_id)+ ".json")
with open(self.path, 'w') as outfile:
json.dump(data, outfile, indent=4, sort_keys=True)
self.cbpi.notify(title="Dashboard {}".format(dashboard_id), message="Saved Successfully", type=NotificationType.SUCCESS)
return {"status": "OK"}
async def delete_content(self, dashboard_id):
self.path = os.path.join(".", 'config', "cbpi_dashboard_"+ str(dashboard_id)+ ".json")
self.path = self.cbpi.config_folder.get_file_path("cbpi_dashboard_"+ str(dashboard_id)+ ".json")
if os.path.exists(self.path):
os.remove(self.path)
self.cbpi.notify(title="Dashboard {}".format(dashboard_id), message="Deleted Successfully", type=NotificationType.SUCCESS)
async def get_custom_widgets(self):
path = os.path.join(".", 'config', "dashboard", "widgets")
path = os.path.join(self.cbpi.config_folder.get_file_path("dashboard"), "widgets")
onlyfiles = [os.path.splitext(f)[0] for f in sorted(listdir(path)) if isfile(join(path, f)) and f.endswith(".svg")]
return onlyfiles

View file

@ -22,7 +22,7 @@ class FermentationController:
self.update_key = "fermenterupdate"
self.cbpi = cbpi
self.logger = logging.getLogger(__name__)
self.path = os.path.join(".", 'config', "fermenter_data.json")
self.path = self.cbpi.config_folder.get_file_path("fermenter_data.json")
self.data = []
self.types = {}
self.steptypes = {}
@ -35,16 +35,16 @@ class FermentationController:
pass
def check_fermenter_file(self):
if os.path.exists(os.path.join(".", 'config', "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")
data = {
"data": [
]
}
destfile = os.path.join(".", 'config', "fermenter_data.json")
destfile = self.cbpi.config_folder.get_file_path("fermenter_data.json")
json.dump(data,open(destfile,'w'),indent=4, sort_keys=True)
pathlib.Path(os.path.join(".", 'config/fermenterrecipes')).mkdir(parents=True, exist_ok=True)
pathlib.Path(self.cbpi.config_folder.get_file_path("fermenterrecipes")).mkdir(parents=True, exist_ok=True)
async def shutdown(self, app=None, fermenterid=None):
self.save()
@ -525,7 +525,7 @@ class FermentationController:
async def savetobook(self, fermenterid):
name = shortuuid.uuid()
path = os.path.join(".", 'config', "fermenterrecipes", "{}.yaml".format(name))
path = self.cbpi.config_folder.get_fermenter_recipe_by_id(name)
fermenter=self._find_by_id(fermenterid)
try:
brewname = fermenter.brewname

View file

@ -29,27 +29,25 @@ class FermenterRecipeController:
async def create(self, name):
id = shortuuid.uuid()
path = os.path.join(".", 'config', "fermenterrecipes", "{}.yaml".format(id))
path = self.cbpi.config_folder.get_fermenter_recipe_by_id(id)
data = dict(basic=dict(name=name), steps=[])
with open(path, "w") as file:
yaml.dump(data, file)
return id
async def save(self, name, data):
path = os.path.join(".", 'config', "fermenterrecipes", "{}.yaml".format(name))
path = self.cbpi.config_folder.get_fermenter_recipe_by_id(name)
logging.info(data)
with open(path, "w") as file:
yaml.dump(data, file, indent=4, sort_keys=True)
async def get_recipes(self):
path = os.path.join(".", 'config', "fermenterrecipes")
onlyfiles = [os.path.splitext(f)[0] for f in listdir(path) if isfile(join(path, f)) and f.endswith(".yaml")]
fermenter_recipes = self.cbpi.config_folder.get_all_fermenter_recipes()
result = []
for filename in onlyfiles:
recipe_path = os.path.join(".", 'config', "fermenterrecipes", "%s.yaml" % filename)
with open(recipe_path) as file:
for filename in fermenter_recipes:
with open(filename) as file:
data = yaml.load(file, Loader=yaml.FullLoader)
dataset = data["basic"]
dataset["file"] = filename
@ -58,27 +56,24 @@ class FermenterRecipeController:
return result
async def get_by_name(self, name):
recipe_path = os.path.join(".", 'config', "fermenterrecipes", "%s.yaml" % name)
recipe_path = self.cbpi.config_folder.get_fermenter_recipe_by_id(name)
with open(recipe_path) as file:
return yaml.load(file, Loader=yaml.FullLoader)
async def remove(self, name):
path = os.path.join(".", 'config', "fermenterrecipes", "{}.yaml".format(name))
path = self.cbpi.config_folder.get_fermenter_recipe_by_id(name)
os.remove(path)
async def brew(self, recipeid, fermenterid, name):
recipe_path = self.cbpi.config_folder.get_fermenter_recipe_by_id(recipeid)
recipe_path = os.path.join(".", 'config', "fermenterrecipes", "%s.yaml" % recipeid)
logging.info(recipe_path)
with open(recipe_path) as file:
data = yaml.load(file, Loader=yaml.FullLoader)
await self.cbpi.fermenter.load_recipe(data, fermenterid, name)
async def clone(self, id, new_name):
recipe_path = os.path.join(".", 'config', "fermenterrecipes", "%s.yaml" % id)
recipe_path = self.cbpi.config_folder.get_fermenter_recipe_by_id(id)
with open(recipe_path) as file:
data = yaml.load(file, Loader=yaml.FullLoader)
data["basic"]["name"] = new_name

View file

@ -29,26 +29,26 @@ class RecipeController:
async def create(self, name):
id = shortuuid.uuid()
path = os.path.join(".", 'config', "recipes", "{}.yaml".format(id))
path = self.cbpi.config_folder.get_recipe_file_by_id(id)
data = dict(basic=dict(name=name, author=self.cbpi.config.get("AUTHOR", "John Doe")), steps=[])
with open(path, "w") as file:
yaml.dump(data, file)
return id
async def save(self, name, data):
path = os.path.join(".", 'config', "recipes", "{}.yaml".format(name))
path = self.cbpi.config_folder.get_recipe_file_by_id(name)
logging.info(data)
with open(path, "w") as file:
yaml.dump(data, file, indent=4, sort_keys=True)
async def get_recipes(self):
path = os.path.join(".", 'config', "recipes")
path = self.cbpi.config_folder.get_file_path("recipes")
onlyfiles = [os.path.splitext(f)[0] for f in listdir(path) if isfile(join(path, f)) and f.endswith(".yaml")]
result = []
for filename in onlyfiles:
recipe_path = os.path.join(".", 'config', "recipes", "%s.yaml" % filename)
recipe_path = self.cbpi.config_folder.get_recipe_file_by_id(filename)
with open(recipe_path) as file:
data = yaml.load(file, Loader=yaml.FullLoader)
dataset = data["basic"]
@ -58,25 +58,25 @@ class RecipeController:
async def get_by_name(self, name):
recipe_path = os.path.join(".", 'config', "recipes", "%s.yaml" % name)
recipe_path = self.cbpi.config_folder.get_recipe_file_by_id(name)
with open(recipe_path) as file:
return yaml.load(file, Loader=yaml.FullLoader)
async def remove(self, name):
path = os.path.join(".", 'config', "recipes", "{}.yaml".format(name))
path = self.cbpi.config_folder.get_recipe_file_by_id(name)
os.remove(path)
async def brew(self, name):
recipe_path = os.path.join(".", 'config', "recipes", "%s.yaml" % name)
recipe_path = self.cbpi.config_folder.get_recipe_file_by_id(name)
with open(recipe_path) as file:
data = yaml.load(file, Loader=yaml.FullLoader)
await self.cbpi.step.load_recipe(data)
async def clone(self, id, new_name):
recipe_path = os.path.join(".", 'config', "recipes", "%s.yaml" % id)
recipe_path = self.cbpi.config_folder.get_recipe_file_by_id(id)
with open(recipe_path) as file:
data = yaml.load(file, Loader=yaml.FullLoader)
data["basic"]["name"] = new_name

View file

@ -19,7 +19,7 @@ class StepController:
def __init__(self, cbpi):
self.cbpi = cbpi
self.logger = logging.getLogger(__name__)
self.path = os.path.join(".", 'config', "step_data.json")
self.path = self.cbpi.config_folder.get_file_path("step_data.json")
#self._loop = asyncio.get_event_loop()
self.basic_data = {}
self.step = None
@ -324,7 +324,7 @@ class StepController:
async def savetobook(self):
name = shortuuid.uuid()
path = os.path.join(".", 'config', "recipes", "{}.yaml".format(name))
path = os.path.join(self.cbpi.config_folder.get_file_path("recipes"), "{}.yaml".format(name))
data = dict(basic=self.basic_data, steps=list(map(lambda item: item.to_dict(), self.profile)))
with open(path, "w") as file:
yaml.dump(data, file)

View file

@ -38,7 +38,7 @@ class SystemController:
async def backupConfig(self):
output_filename = "cbpi4_config"
dir_name = pathlib.Path(os.path.join(".", 'config'))
dir_name = pathlib.Path(self.cbpi.config_folder.get_file_path(''))
shutil.make_archive(output_filename, 'zip', dir_name)
async def downloadlog(self, logtime):
@ -153,7 +153,7 @@ class SystemController:
try:
content = svg_file.read().decode('utf-8','replace')
if svg_file and self.allowed_file(filename, 'svg'):
self.path = os.path.join(".","config","dashboard","widgets", filename)
self.path = os.path.join(self.cbpi.config_folder.get_file_path("dashboard"),"widgets", filename)
logging.info(self.path)
f=open(self.path, "w")

View file

@ -32,7 +32,7 @@ class UploadController:
async def get_kbh_recipes(self):
try:
path = os.path.join(".", 'config', "upload", "kbh.db")
path = self.cbpi.config_folder.get_upload_file("kbh.db")
conn = sqlite3.connect(path)
c = conn.cursor()
c.execute('SELECT ID, Sudname, Status FROM Sud')
@ -47,7 +47,7 @@ class UploadController:
async def get_xml_recipes(self):
try:
path = os.path.join(".", 'config', "upload", "beer.xml")
path = self.cbpi.config_folder.get_upload_file("beer.xml")
e = xml.etree.ElementTree.parse(path).getroot()
result =[]
counter = 1
@ -61,7 +61,7 @@ class UploadController:
async def get_json_recipes(self):
try:
path = os.path.join(".", 'config', "upload", "mmum.json")
path = self.cbpi.config_folder.get_upload_file("mmum.json")
e = json.load(open(path))
result =[]
result.append({'value': str(1), 'label': e['Name']})
@ -123,7 +123,7 @@ class UploadController:
try:
beer_xml = recipe_file.read().decode('utf-8','replace')
if recipe_file and self.allowed_file(filename, 'xml'):
self.path = os.path.join(".", 'config', "upload", "beer.xml")
self.path = self.cbpi.config_folder.get_upload_file("beer.xml")
f = open(self.path, "w")
f.write(beer_xml)
@ -137,7 +137,7 @@ class UploadController:
try:
mmum_json = recipe_file.read().decode('utf-8','replace')
if recipe_file and self.allowed_file(filename, 'json'):
self.path = os.path.join(".", 'config', "upload", "mmum.json")
self.path = self.cbpi.config_folder.get_upload_file("mmum.json")
f = open(self.path, "w")
f.write(mmum_json)
@ -151,7 +151,7 @@ class UploadController:
try:
content = recipe_file.read()
if recipe_file and self.allowed_file(filename, 'sqlite'):
self.path = os.path.join(".", 'config', "upload", "kbh.db")
self.path = self.cbpi.config_folder.get_upload_file("kbh.db")
f=open(self.path, "wb")
f.write(content)
@ -168,7 +168,7 @@ class UploadController:
config = self.get_config_values()
if self.kettle is not None:
# load beerxml file located in upload folder
self.path = os.path.join(".", 'config', "upload", "kbh.db")
self.path = self.cbpi.config_folder.get_upload_file("kbh.db")
if os.path.exists(self.path) is False:
self.cbpi.notify("File Not Found", "Please upload a kbh V2 databsel file", NotificationType.ERROR)
@ -318,7 +318,7 @@ class UploadController:
self.cbpi.notify('Recipe Upload', 'No default Kettle defined. Please specify default Kettle in settings', NotificationType.ERROR)
def findMax(self, string):
self.path = os.path.join(".", 'config', "upload", "mmum.json")
self.path = self.cbpi.config_folder.get_upload_file("mmum.json")
e = json.load(open(self.path))
for idx in range(1,20):
search_string = string.replace("%%",str(idx))
@ -328,7 +328,7 @@ class UploadController:
return i
def getJsonMashin(self, id):
self.path = os.path.join(".", 'config', "upload", "mmum.json")
self.path = self.cbpi.config_folder.get_upload_file("mmum.json")
e = json.load(open(self.path))
return float(e['Infusion_Einmaischtemperatur'])
@ -337,7 +337,7 @@ class UploadController:
if self.kettle is not None:
# load mmum-json file located in upload folder
self.path = os.path.join(".", 'config', "upload", "mmum.json")
self.path = self.cbpi.config_folder.get_upload_file("mmum.json")
if os.path.exists(self.path) is False:
self.cbpi.notify("File Not Found", "Please upload a MMuM-JSON File", NotificationType.ERROR)
@ -551,7 +551,7 @@ class UploadController:
if self.kettle is not None:
# load beerxml file located in upload folder
self.path = os.path.join(".", 'config', "upload", "beer.xml")
self.path = self.cbpi.config_folder.get_upload_file("beer.xml")
if os.path.exists(self.path) is False:
self.cbpi.notify("File Not Found", "Please upload a Beer.xml File", NotificationType.ERROR)
@ -692,7 +692,7 @@ class UploadController:
temp = round(9.0 / 5.0 * float(e.find("STEP_TEMP").text) + 32, 2)
steps.append({"name": e.find("NAME").text, "temp": temp, "timer": float(e.find("STEP_TIME").text)})
elif recipe_type == "json":
self.path = os.path.join(".", 'config', "upload", "mmum.json")
self.path = self.cbpi.config_folder.get_upload_file("mmum.json")
e = json.load(open(self.path))
for idx in range(1,self.findMax("Infusion_Rastzeit%%")):
if self.cbpi.config.get("TEMP_UNIT", "C") == "C":

View file

@ -86,7 +86,7 @@ async def error_middleware(request, handler):
class CraftBeerPi:
def __init__(self):
def __init__(self, configFolder):
operationsystem= sys.platform
if operationsystem.startswith('win'):
@ -97,7 +97,8 @@ class CraftBeerPi:
self.version = __version__
self.codename = __codename__
self.static_config = load_config(os.path.join(".", 'config', "config.yaml"))
self.config_folder = configFolder
self.static_config = load_config(configFolder.get_file_path("config.yaml"))
logger.info("Init CraftBeerPI")

View file

@ -24,6 +24,32 @@ class ActorHttpEndpoints():
"""
return web.json_response(data=self.controller.get_state())
@request_mapping(path="/{id:\w+}", auth_required=False)
async def http_get_one(self, request):
"""
---
description: Get one Actor
tags:
- Actor
parameters:
- name: "id"
in: "path"
description: "Actor ID"
required: true
type: "integer"
format: "int64"
responses:
"200":
description: successful operation
"404":
description: Actor not found
"""
actor = self.controller.find_by_id(request.match_info['id'])
if (actor is None):
return web.json_response(status=404)
return web.json_response(data=actor.to_dict(), status=200)
@request_mapping(path="/", method="POST", auth_required=False)
async def http_add(self, request):

View file

@ -12,7 +12,7 @@ class DashBoardHttpEndpoints:
def __init__(self, cbpi):
self.cbpi = cbpi
self.controller = cbpi.dashboard
self.cbpi.register(self, "/dashboard", os.path.join(".","config", "dashboard", "widgets"))
self.cbpi.register(self, "/dashboard", os.path.join(cbpi.config_folder.get_file_path("dashboard"), "widgets"))
@request_mapping(path="/{id:\d+}/content", auth_required=False)

Binary file not shown.

View file

@ -22,3 +22,5 @@ psutil==5.9.0
zipp>=0.5
PyInquirer==1.0.3
colorama==0.4.4
pytest-aiohttp
coverage==6.3.1

2
run.py
View file

@ -1,3 +1,3 @@
from cbpi.cli import main
main()
main(auto_envvar_prefix='CBPI')

View file

@ -0,0 +1,12 @@
{
"data": [
{
"id": "3CUJte4bkxDMFCtLX8eqsX",
"name": "SomeActor",
"power": 100,
"props": {},
"state": false,
"type": "DummyActor"
}
]
}

View file

@ -0,0 +1,6 @@
[Desktop Entry]
Type=Application
Name=Chromium
Comment=Chromium Webbrowser
NoDisplay=false
Exec=chromium-browser --noerrordialogs --disable-session-crashed-bubble --disable-infobars --force-device-scale-factor=1.00 --start-fullscreen "http://localhost:8000"

View file

@ -0,0 +1,148 @@
{
"AUTHOR": {
"description": "Author",
"name": "AUTHOR",
"options": null,
"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",
"options": [
{
"label": "Yes",
"value": "Yes"
},
{
"label": "No",
"value": "No"
}
],
"type": "select",
"value": "Yes"
},
"RECIPE_CREATION_PATH": {
"description": "API path to creation plugin. Default: empty",
"name": "RECIPE_CREATION_PATH",
"options": null,
"type": "string",
"value": ""
},
"brewfather_api_key": {
"description": "Brewfather API Kay",
"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": ""
},
"TEMP_UNIT": {
"description": "Temperature Unit",
"name": "TEMP_UNIT",
"options": [
{
"label": "C",
"value": "C"
},
{
"label": "F",
"value": "F"
}
],
"type": "select",
"value": "C"
},
"AutoMode": {
"description": "Use AutoMode in steps",
"name": "AutoMode",
"options": [
{
"label": "Yes",
"value": "Yes"
},
{
"label": "No",
"value": "No"
}
],
"type": "select",
"value": "Yes"
},
"steps_boil": {
"description": "Boil step type",
"name": "steps_boil",
"options": null,
"type": "step",
"value": "BoilStep"
},
"steps_boil_temp": {
"description": "Default Boil Temperature for Recipe Creation",
"name": "steps_boil_temp",
"options": null,
"type": "number",
"value": "99"
},
"steps_cooldown": {
"description": "Cooldown step type",
"name": "steps_cooldown",
"options": null,
"type": "step",
"value": "CooldownStep"
},
"steps_cooldown_sensor": {
"description": "Alternative Sensor to monitor temperature durring cooldown (if not selected, Kettle Sensor will be used)",
"name": "steps_cooldown_sensor",
"options": null,
"type": "sensor",
"value": ""
},
"steps_cooldown_temp": {
"description": "Cooldown temp will send notification when this temeprature is reached",
"name": "steps_cooldown_temp",
"options": null,
"type": "number",
"value": "20"
},
"steps_mash": {
"description": "Mash step type",
"name": "steps_mash",
"options": null,
"type": "step",
"value": "MashStep"
},
"steps_mashin": {
"description": "MashIn step type",
"name": "steps_mashin",
"options": null,
"type": "step",
"value": "MashInStep"
},
"steps_mashout": {
"description": "MashOut step type",
"name": "steps_mashout",
"options": null,
"type": "step",
"value": "NotificationStep"
}
}

View file

@ -0,0 +1,20 @@
name: CraftBeerPi
version: 4.0.8
index_url: /cbpi_ui/static/index.html
port: 8000
mqtt: false
mqtt_host: localhost
mqtt_port: 1883
mqtt_username: ""
mqtt_password: ""
username: cbpi
password: 123
plugins:
- cbpi4ui

View file

@ -0,0 +1,9 @@
[Unit]
Description=Craftbeer Pi
[Service]
WorkingDirectory=/home/pi
ExecStart=/usr/local/bin/cbpi start
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,62 @@
#!/bin/sh
### BEGIN INIT INFO
# Provides: craftbeerpi
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Put a short description of the service here
# Description: Put a long description of the service here
### END INIT INFO
# Change the next 3 lines to suit where you install your script and what you want to call it
DIR=#DIR#
DAEMON=$DIR/cbpi
DAEMON_NAME=CraftBeerPI
# Add any command line options for your daemon here
DAEMON_OPTS=""
# This next line determines what user the script runs as.
# Root generally not recommended but necessary if you are using the Raspberry Pi GPIO from Python.
DAEMON_USER=root
# The process ID of the script when it runs is stored here:
PIDFILE=/var/run/$DAEMON_NAME.pid
. /lib/lsb/init-functions
do_start () {
log_daemon_msg "Starting system $DAEMON_NAME daemon"
start-stop-daemon --start --background --pidfile $PIDFILE --make-pidfile --user $DAEMON_USER --chuid $DAEMON_USER --chdir $DIR --startas $DAEMON -- $DAEMON_OPTS
log_end_msg $?
}
do_stop () {
log_daemon_msg "Stopping system $DAEMON_NAME daemon"
start-stop-daemon --stop --pidfile $PIDFILE --retry 10
log_end_msg $?
}
case "$1" in
start|stop)
do_${1}
;;
restart|reload|force-reload)
do_stop
do_start
;;
status)
status_of_proc "$DAEMON_NAME" "$DAEMON" && exit 0 || exit $?
;;
*)
echo "Usage: /etc/init.d/$DAEMON_NAME {start|stop|restart|status}"
exit 1
;;
esac
exit 0

View file

@ -0,0 +1,3 @@
{
"elements": []
}

View file

@ -0,0 +1,5 @@
{
"data": [
]
}

View file

@ -0,0 +1,5 @@
{
"data": [
]
}

View file

@ -0,0 +1,3 @@
cbpi4-ui:
installation_date: '2021-01-06 16:03:31'
version: '0.0.1'

View file

View file

@ -0,0 +1,5 @@
{
"data": [
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

View file

@ -0,0 +1,8 @@
{
"basic": {
"name": ""
},
"steps": [
]
}

View file

View file

@ -0,0 +1,23 @@
# content of conftest.py
from codecs import ignore_errors
from distutils.command.config import config
import os
from cbpi.configFolder import ConfigFolder
from cbpi.craftbeerpi import CraftBeerPi
from aiohttp.test_utils import AioHTTPTestCase
from distutils.dir_util import copy_tree
class CraftBeerPiTestCase(AioHTTPTestCase):
async def get_application(self):
self.config_folder = self.configuration()
self.cbpi = CraftBeerPi(self.config_folder)
await self.cbpi.init_serivces()
return self.cbpi.app
def configuration(self):
test_directory = os.path.dirname(__file__)
test_config_directory = os.path.join(test_directory, 'cbpi-test-config')
configFolder = ConfigFolder(test_config_directory)
return configFolder

Binary file not shown.

View file

@ -1,66 +1,39 @@
import logging
from unittest import mock
from unittest.mock import MagicMock, Mock
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
from cbpi.craftbeerpi import CraftBeerPi
from aiohttp.test_utils import unittest_run_loop
from tests.cbpi_config_fixture import CraftBeerPiTestCase
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
class ActorTestCase(AioHTTPTestCase):
async def get_application(self):
self.cbpi = CraftBeerPi()
await self.cbpi.init_serivces()
return self.cbpi.app
@unittest_run_loop
async def test_actor_mock(self):
with mock.patch.object(self.cbpi.bus, 'fire', wraps=self.cbpi.bus.fire) as mock_obj:
# Send HTTP POST
resp = await self.client.request("POST", "/actor/1/on")
# Check Result
assert resp.status == 204
# Check if Event are fired
assert mock_obj.call_count == 2
class ActorTestCase(CraftBeerPiTestCase):
@unittest_run_loop
async def test_actor_switch(self):
resp = await self.client.post(path="/login", data={"username": "cbpi", "password": "123"})
assert resp.status == 200
assert resp.status == 200, "login should be successful"
resp = await self.client.request("POST", "/actor/1/on")
assert resp.status == 204
i = await self.cbpi.actor.get_one(1)
resp = await self.client.request("POST", "/actor/3CUJte4bkxDMFCtLX8eqsX/on")
assert resp.status == 204, "switching actor on should work"
i = self.cbpi.actor.find_by_id("3CUJte4bkxDMFCtLX8eqsX")
assert i.instance.state is True
resp = await self.client.request("POST", "/actor/1/off")
resp = await self.client.request("POST", "/actor/3CUJte4bkxDMFCtLX8eqsX/off")
assert resp.status == 204
i = await self.cbpi.actor.get_one(1)
assert i.instance.state is False
resp = await self.client.request("POST", "/actor/1/toggle")
assert resp.status == 204
i = await self.cbpi.actor.get_one(1)
assert i.instance.state is True
resp = await self.client.request("POST", "/actor/1/toggle")
assert resp.status == 204
i = await self.cbpi.actor.get_one(1)
i = self.cbpi.actor.find_by_id("3CUJte4bkxDMFCtLX8eqsX")
assert i.instance.state is False
@unittest_run_loop
async def test_crud(self):
data = {
"name": "CustomActor",
"type": "CustomActor",
"config": {
"interval": 5
}
"name": "SomeActor",
"power": 100,
"props": {
},
"state": False,
"type": "DummyActor"
}
# Add new sensor

View file

@ -1,20 +1,16 @@
import logging
import unittest
from cbpi.cli import CraftBeerPiCli
from cli import add, remove, list_plugins
from cbpi.configFolder import ConfigFolder
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
class CLITest(unittest.TestCase):
def test_install(self):
assert add("cbpi4-ui-plugin") == True
assert add("cbpi4-ui-plugin") == False
assert remove("cbpi4-ui-plugin") == True
def test_list(self):
list_plugins()
cli = CraftBeerPiCli(ConfigFolder("./cbpi-test-config"))
cli.plugins_list()
if __name__ == '__main__':
unittest.main()

View file

@ -1,54 +1,31 @@
import time
import aiosqlite
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
from cbpi.api.config import ConfigType
from cbpi.craftbeerpi import CraftBeerPi
class ConfigTestCase(AioHTTPTestCase):
async def get_application(self):
self.cbpi = CraftBeerPi()
await self.cbpi.init_serivces()
return self.cbpi.app
from aiohttp.test_utils import unittest_run_loop
from tests.cbpi_config_fixture import CraftBeerPiTestCase
class ConfigTestCase(CraftBeerPiTestCase):
@unittest_run_loop
async def test_get(self):
assert self.cbpi.config.get("CBPI_TEST_1", 1) == "22"
assert self.cbpi.config.get("steps_boil_temp", 1) == "99"
@unittest_run_loop
async def test_set_get(self):
value = str(time.time())
value = 35
await self.cbpi.config.set("CBPI_TEST_2", value)
assert self.cbpi.config.get("CBPI_TEST_2", 1) == value
@unittest_run_loop
async def test_add(self):
value = str(time.time())
key = "CBPI_TEST_3"
async with aiosqlite.connect("./craftbeerpi.db") as db:
await db.execute("DELETE FROM config WHERE name = ? ", (key,))
await db.commit()
await self.cbpi.config.add(key, value, type=ConfigType.STRING, description="test")
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 = str(time.time())
key = "CBPI_TEST_3"
await self.cbpi.config.set(key, value)
assert self.cbpi.config.get(key, 1) == value
value = "Some New Brewery Name"
key = "BREWERY_NAME"
resp = await self.client.request("PUT", "/config/%s/" % key, json={'value': '1'})
resp = await self.client.request("PUT", "/config/%s/" % key, json={'value': value})
assert resp.status == 204
assert self.cbpi.config.get(key, -1) == "1"
assert self.cbpi.config.get(key, -1) == value
@unittest_run_loop
async def test_http_get(self):
@ -57,5 +34,5 @@ class ConfigTestCase(AioHTTPTestCase):
@unittest_run_loop
async def test_get_default(self):
value = self.cbpi.config.get("HELLO_WORLD", None)
assert value == None
value = self.cbpi.config.get("HELLO_WORLD", "DefaultValue")
assert value == "DefaultValue"

View file

@ -1,18 +1,10 @@
import aiohttp
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
from aiohttp.test_utils import unittest_run_loop
from tests.cbpi_config_fixture import CraftBeerPiTestCase
from cbpi.craftbeerpi import CraftBeerPi
class DashboardTestCase(AioHTTPTestCase):
async def get_application(self):
self.cbpi = CraftBeerPi()
await self.cbpi.init_serivces()
return self.cbpi.app
class DashboardTestCase(CraftBeerPiTestCase):
@unittest_run_loop
async def test_crud(self):
@ -28,56 +20,12 @@ class DashboardTestCase(AioHTTPTestCase):
"config": {}
}
resp = await self.client.get(path="/dashboard")
resp = await self.client.get(path="/dashboard/current")
assert resp.status == 200
# Add new dashboard
resp = await self.client.post(path="/dashboard/", json=data)
assert resp.status == 200
m = await resp.json()
dashboard_id = m["id"]
# Get dashboard
resp = await self.client.get(path="/dashboard/%s" % dashboard_id)
assert resp.status == 200
m2 = await resp.json()
dashboard_id = m2["id"]
# Update dashboard
resp = await self.client.put(path="/dashboard/%s" % dashboard_id, json=m)
assert resp.status == 200
dashboard_id = await resp.json()
# Add dashboard content
dashboard_content["dbid"] = dashboard_id
resp = await self.client.post(path="/dashboard/%s/content" % dashboard_id, json=dashboard_content)
assert resp.status == 200
m_content = await resp.json()
print("CONTENT", m_content)
content_id = m_content["id"]
# Get dashboard
resp = await self.client.get(path="/dashboard/%s/content" % (dashboard_id))
assert resp.status == 200
resp = await self.client.post(path="/dashboard/%s/content/%s/move" % (dashboard_id, content_id), json=dict(x=1,y=1))
assert resp.status == 200
resp = await self.client.delete(path="/dashboard/%s/content/%s" % (dashboard_id, content_id))
assert resp.status == 204
# Delete dashboard
resp = await self.client.delete(path="/dashboard/%s" % dashboard_id)
assert resp.status == 204
@unittest_run_loop
async def test_dashboard_controller(self):
result = await self.cbpi.dashboard.get_all()
print(result)
await self.cbpi.dashboard.add(**{"name":"Tewst"})
print(await self.cbpi.dashboard.get_one(1))
await self.cbpi.dashboard.add_content(dict(dbid=1,element_id=1,type="test",config={"name":"Manue"}))
await self.cbpi.dashboard.move_content(1,2,3)

View file

@ -1,116 +0,0 @@
import asyncio
from cbpi.api.dataclasses import Fermenter, FermenterStep, Props, Step
import logging
from unittest import mock
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
from cbpi.craftbeerpi import CraftBeerPi
from cbpi.controller.fermentation_controller import FermenationController
import unittest
import json
from aiohttp import web
from unittest.mock import MagicMock, Mock
logging.basicConfig(level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
class FermenterTest(AioHTTPTestCase):
async def get_application(self):
app = web.Application()
return app
def create_file(self):
data = [
{
"id": "f1",
"name": "Fermenter1",
"props": {},
"steps": [
{
"id": "f1s1",
"name": "Step1",
"props": {},
"state_text": "",
"status": "I",
"type": "T2"
},
{
"id": "f1s2",
"name": "Step2",
"props": {},
"state_text": "",
"status": "I",
"type": "T1"
},
],
"target_temp": 0
},
{
"id": "f2",
"name": "Fermenter2",
"props": {},
"steps": [
{
"id": "f2s1",
"name": "Step1",
"props": {},
"state_text": "",
"status": "I",
"type": "T1"
},
{
"id": "f2s2",
"name": "Step2",
"props": {},
"state_text": "",
"status": "I",
"type": "T2"
},
],
"target_temp": 0
}
]
with open("./config/fermenter_data.json", "w") as file:
json.dump(data, file, indent=4, sort_keys=True)
@unittest_run_loop
async def test_actor_mock(self):
self.create_file()
mock = Mock()
f = FermenationController(mock)
f.types = {
"T1": {"name": "T2", "class": FermenterStep, "properties": [], "actions": []},
"T2": {"name": "T2", "class": FermenterStep, "properties": [], "actions": []}
}
await f.load()
#ferm = Fermenter(name="Maneul")
# item = await f.create(ferm)
# await f.create_step(item.id, Step(name="Manuel"))
# await f.delete(item.id)
item = await f.get("f1")
await f.start("f1")
await f.start("f2")
await asyncio.sleep(3)
# await f.create_step(item.id, Step(name="MANUEL", props=Props()))
#await f.start(item.id)
#await asyncio.sleep(1)
#await f.next(item.id)
#await asyncio.sleep(1)
#await f.next(item.id)
#await asyncio.sleep(1)
#await f.next(item.id)
#await asyncio.sleep(1)
#await f.move_step("f1", "f1s1", 1)
# await f.reset(item.id)
await f.shutdown()
if __name__ == '__main__':
unittest.main()

View file

@ -15,13 +15,6 @@ except Exception:
patcher.start()
import RPi.GPIO as GPIO
class HelloWorld(object):
def test(self, a):
return a
class TestSwitch(unittest.TestCase):
GPIO_NUM = 22
@ -36,14 +29,3 @@ class TestSwitch(unittest.TestCase):
def test_switch_without_scheduler_starts_disabled(self, patched_output):
GPIO.output(self.GPIO_NUM, GPIO.LOW)
patched_output.assert_called_once_with(self.GPIO_NUM, GPIO.LOW)
def test_hello_world(self):
h = HelloWorld()
with mock.patch.object(HelloWorld, 'test', wraps=h.test) as fake_increment:
#print(h.test("HALLO"))
print(h.test("ABC"))
print(fake_increment.call_args)
print(h.test("HALLO"))
print(fake_increment.call_args_list)

View file

@ -1,14 +1,8 @@
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
from cbpi.craftbeerpi import CraftBeerPi
from aiohttp.test_utils import unittest_run_loop
from tests.cbpi_config_fixture import CraftBeerPiTestCase
class IndexTestCase(AioHTTPTestCase):
async def get_application(self):
self.cbpi = CraftBeerPi()
await self.cbpi.init_serivces()
return self.cbpi.app
class IndexTestCase(CraftBeerPiTestCase):
@unittest_run_loop
async def test_index(self):

View file

@ -1,52 +1,16 @@
import asyncio
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
from cbpi.craftbeerpi import CraftBeerPi
from aiohttp.test_utils import unittest_run_loop
from tests.cbpi_config_fixture import CraftBeerPiTestCase
class KettleTestCase(AioHTTPTestCase):
async def get_application(self):
self.cbpi = CraftBeerPi()
await self.cbpi.init_serivces()
return self.cbpi.app
class KettleTestCase(CraftBeerPiTestCase):
@unittest_run_loop
async def test_get(self):
resp = await self.client.request("GET", "/kettle")
assert resp.status == 200
print(await resp.json())
@unittest_run_loop
async def test_heater(self):
resp = await self.client.get("/kettle/1/heater/on")
assert resp.status == 204
resp = await self.client.get("/kettle/1/heater/off")
assert resp.status == 204
@unittest_run_loop
async def test_agitator(self):
resp = await self.client.get("/kettle/1/agitator/on")
assert resp.status == 204
resp = await self.client.get("/kettle/1/agitator/off")
assert resp.status == 204
@unittest_run_loop
async def test_temp(self):
resp = await self.client.get("/kettle/1/temp")
assert resp.status == 204
resp = await self.client.get("/kettle/1/targettemp")
assert resp.status == 200
@unittest_run_loop
async def test_automatic(self):
resp = await self.client.post("/kettle/1/automatic")
assert resp.status == 204
kettle = resp.json()
assert kettle != None
@unittest_run_loop
async def test_crud(self):
@ -69,21 +33,21 @@ class KettleTestCase(AioHTTPTestCase):
m = await resp.json()
sensor_id = m["id"]
kettle_id = m["id"]
print("KETTLE", m["id"], m)
# Get sensor
resp = await self.client.get(path="/kettle/%s" % sensor_id)
# Update Kettle
resp = await self.client.put(path="/kettle/%s" % kettle_id, json=m)
assert resp.status == 200
m2 = await resp.json()
sensor_id = m2["id"]
# Update Sensor
resp = await self.client.put(path="/kettle/%s" % sensor_id, json=m)
assert resp.status == 200
# Set Kettle target temp
resp = await self.client.post(path="/kettle/%s/target_temp" % kettle_id, json={"temp":75})
assert resp.status == 204
# # Delete Sensor
resp = await self.client.delete(path="/kettle/%s" % sensor_id)
resp = await self.client.delete(path="/kettle/%s" % kettle_id)
assert resp.status == 204

View file

@ -1,21 +1,16 @@
import asyncio
import glob
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
from aiohttp.test_utils import unittest_run_loop
from tests.cbpi_config_fixture import CraftBeerPiTestCase
import os
from cbpi.craftbeerpi import CraftBeerPi, load_config
class UtilsTestCase(AioHTTPTestCase):
async def get_application(self):
self.cbpi = CraftBeerPi()
await self.cbpi.init_serivces()
return self.cbpi.app
class LoggerTestCase(CraftBeerPiTestCase):
@unittest_run_loop
async def test_log_data(self):
os.makedirs("./logs", exist_ok=True)
log_name = "test"
#clear all logs
self.cbpi.log.clear_log(log_name)

View file

@ -1,16 +1,8 @@
import aiohttp
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
from cbpi.craftbeerpi import CraftBeerPi
from aiohttp.test_utils import unittest_run_loop
from tests.cbpi_config_fixture import CraftBeerPiTestCase
class NotificationTestCase(AioHTTPTestCase):
async def get_application(self):
self.cbpi = CraftBeerPi()
await self.cbpi.init_serivces()
return self.cbpi.app
class NotificationTestCase(CraftBeerPiTestCase):
@unittest_run_loop
async def test_actor_switch(self):

View file

@ -1,14 +1,8 @@
import asyncio
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
from cbpi.craftbeerpi import CraftBeerPi
from aiohttp.test_utils import unittest_run_loop
from tests.cbpi_config_fixture import CraftBeerPiTestCase
class SensorTestCase(AioHTTPTestCase):
async def get_application(self):
self.cbpi = CraftBeerPi()
await self.cbpi.init_serivces()
return self.cbpi.app
class SensorTestCase(CraftBeerPiTestCase):
@unittest_run_loop
async def test_crud(self):
@ -28,12 +22,11 @@ class SensorTestCase(AioHTTPTestCase):
m = await resp.json()
sensor_id = m["id"]
# Get sensor
# Get sensor value
resp = await self.client.get(path="/sensor/%s"% sensor_id)
assert resp.status == 200
m2 = await resp.json()
sensor_id = m2["id"]
# Update Sensor
resp = await self.client.put(path="/sensor/%s" % sensor_id, json=m)

View file

@ -1,23 +1,13 @@
import asyncio
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
from cbpi.craftbeerpi import CraftBeerPi
from aiohttp.test_utils import unittest_run_loop
from tests.cbpi_config_fixture import CraftBeerPiTestCase
class StepTestCase(AioHTTPTestCase):
async def get_application(self):
self.cbpi = CraftBeerPi()
await self.cbpi.init_serivces()
return self.cbpi.app
class StepTestCase(CraftBeerPiTestCase):
@unittest_run_loop
async def test_get(self):
resp = await self.client.request("GET", "/step")
print(resp)
assert resp.status == 200
resp = await self.client.request("GET", "/step/types")
resp = await self.client.request("GET", "/step2")
print(resp)
assert resp.status == 200
@ -31,26 +21,19 @@ class StepTestCase(AioHTTPTestCase):
}
# Add new step
resp = await self.client.post(path="/step/", json=data)
resp = await self.client.post(path="/step2/", json=data)
assert resp.status == 200
m = await resp.json()
print("Step", m)
sensor_id = m["id"]
# Get sensor
resp = await self.client.get(path="/step/%s" % sensor_id)
# Update step
resp = await self.client.put(path="/step2/%s" % sensor_id, json=m)
assert resp.status == 200
m2 = await resp.json()
sensor_id = m2["id"]
# Update Sensor
resp = await self.client.put(path="/step/%s" % sensor_id, json=m)
assert resp.status == 200
# # Delete Sensor
resp = await self.client.delete(path="/step/%s" % sensor_id)
# # Delete step
resp = await self.client.delete(path="/step2/%s" % sensor_id)
assert resp.status == 204
def create_wait_callback(self, topic):
@ -67,33 +50,3 @@ class StepTestCase(AioHTTPTestCase):
if future in done:
pass
@unittest_run_loop
async def test_process(self):
step_ctlr = self.cbpi.step
await step_ctlr.clear_all()
await step_ctlr.add(**{"name": "Kettle1", "type": "CustomStepCBPi", "config": {"name1": "1", "temp": 99}})
await step_ctlr.add(**{"name": "Kettle1", "type": "CustomStepCBPi", "config": {"name1": "1", "temp": 99}})
await step_ctlr.add(**{"name": "Kettle1", "type": "CustomStepCBPi", "config": {"name1": "1", "temp": 99}})
await step_ctlr.stop_all()
future = self.create_wait_callback("step/+/started")
await step_ctlr.start()
await self.wait(future)
for i in range(len(step_ctlr.cache)-1):
future = self.create_wait_callback("step/+/started")
await step_ctlr.next()
await self.wait(future)
await self.print_steps()
async def print_steps(self):
s = await self.cbpi.step.get_all()
print(s)
for k, v in s.items():
print(k, v.to_json())

View file

@ -1,46 +0,0 @@
import logging
from unittest import mock
import unittest
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
from cbpi.craftbeerpi import CraftBeerPi
import pprint
import asyncio
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
pp = pprint.PrettyPrinter(indent=4)
class ActorTestCase(AioHTTPTestCase):
async def get_application(self):
self.cbpi = CraftBeerPi()
await self.cbpi.init_serivces()
return self.cbpi.app
'''
@unittest_run_loop
async def test_get_all(self):
resp = await self.client.get(path="/step2")
assert resp.status == 200
@unittest_run_loop
async def test_add_step(self):
resp = await self.client.post(path="/step2", json=dict(name="Manuel"))
data = await resp.json()
assert resp.status == 200
@unittest_run_loop
async def test_delete(self):
resp = await self.client.post(path="/step2", json=dict(name="Manuel"))
data = await resp.json()
assert resp.status == 200
resp = await self.client.delete(path="/step2/%s" % data["id"])
assert resp.status == 204
'''
@unittest_run_loop
async def test_move(self):
await self.cbpi.step2.resume()
if __name__ == '__main__':
unittest.main()

View file

@ -1,19 +1,11 @@
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
from cbpi.craftbeerpi import CraftBeerPi
from aiohttp.test_utils import unittest_run_loop
from tests.cbpi_config_fixture import CraftBeerPiTestCase
class IndexTestCase(AioHTTPTestCase):
async def get_application(self):
self.cbpi = CraftBeerPi()
await self.cbpi.init_serivces()
return self.cbpi.app
class IndexTestCase(CraftBeerPiTestCase):
@unittest_run_loop
async def test_endpoints(self):
# Test Index Page
resp = await self.client.post(path="/system/restart")
assert resp.status == 200
@ -21,9 +13,3 @@ class IndexTestCase(AioHTTPTestCase):
resp = await self.client.post(path="/system/shutdown")
assert resp.status == 200
resp = await self.client.get(path="/system/jobs")
assert resp.status == 200
resp = await self.client.get(path="/system/events")
assert resp.status == 200

View file

@ -1,16 +0,0 @@
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
from cbpi.craftbeerpi import CraftBeerPi, load_config
class UtilsTestCase(AioHTTPTestCase):
async def get_application(self):
self.cbpi = CraftBeerPi()
await self.cbpi.init_serivces()
return self.cbpi.app
@unittest_run_loop
async def test_load_file(self):
assert load_config("") is None

View file

@ -1,56 +1,46 @@
import asyncio
import aiohttp
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
from aiohttp.test_utils import unittest_run_loop
from tests.cbpi_config_fixture import CraftBeerPiTestCase
from cbpi.craftbeerpi import CraftBeerPi
# class WebSocketTestCase(CraftBeerPiTestCase):
# @unittest_run_loop
# async def test_brewing_process(self):
class WebSocketTestCase(AioHTTPTestCase):
# count_step_done = 0
# async with self.client.ws_connect('/ws') as ws:
# await ws.send_json(data=dict(topic="step/stop"))
# await ws.send_json(data=dict(topic="step/start"))
# async for msg in ws:
# if msg.type == aiohttp.WSMsgType.TEXT:
# try:
# msg_obj = msg.json()
# topic = msg_obj.get("topic")
# if topic == "job/step/done":
# count_step_done = count_step_done + 1
# if topic == "step/brewing/finished":
# await ws.send_json(data=dict(topic="close"))
# except Exception as e:
# print(e)
# break
# elif msg.type == aiohttp.WSMsgType.ERROR:
# break
async def get_application(self):
self.cbpi = CraftBeerPi()
await self.cbpi.init_serivces()
return self.cbpi.app
# assert count_step_done == 4
# @unittest_run_loop
# async def test_wrong_format(self):
@unittest_run_loop
async def test_brewing_process(self):
# async with self.client.ws_connect('/ws') as ws:
# await ws.send_json(data=dict(a="close"))
# async for msg in ws:
# print("MSG TYP", msg.type, msg.data)
# if msg.type == aiohttp.WSMsgType.TEXT:
# msg_obj = msg.json()
# if msg_obj["topic"] != "connection/success":
# print(msg.data)
# raise Exception()
count_step_done = 0
async with self.client.ws_connect('/ws') as ws:
await ws.send_json(data=dict(topic="step/stop"))
await ws.send_json(data=dict(topic="step/start"))
async for msg in ws:
if msg.type == aiohttp.WSMsgType.TEXT:
try:
msg_obj = msg.json()
topic = msg_obj.get("topic")
if topic == "job/step/done":
count_step_done = count_step_done + 1
if topic == "step/brewing/finished":
await ws.send_json(data=dict(topic="close"))
except Exception as e:
print(e)
break
elif msg.type == aiohttp.WSMsgType.ERROR:
break
assert count_step_done == 4
@unittest_run_loop
async def test_wrong_format(self):
async with self.client.ws_connect('/ws') as ws:
await ws.send_json(data=dict(a="close"))
async for msg in ws:
print("MSG TYP", msg.type, msg.data)
if msg.type == aiohttp.WSMsgType.TEXT:
msg_obj = msg.json()
if msg_obj["topic"] != "connection/success":
print(msg.data)
raise Exception()
else:
raise Exception()
# else:
# raise Exception()