mirror of
https://github.com/PiBrewing/craftbeerpi4.git
synced 2024-12-22 13:34:55 +01:00
Merge pull request #93 from craftbeerpi/development
Development environment for vscode | spunding
This commit is contained in:
commit
8a0061f0a0
89 changed files with 1708 additions and 1019 deletions
20
.devcontainer/Dockerfile
Normal file
20
.devcontainer/Dockerfile
Normal 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/development.zip -L -o /opt/downloads/cbpi-ui.zip \
|
||||
&& pip3 install --no-cache-dir /opt/downloads/cbpi-ui.zip \
|
||||
&& rm -rf /opt/downloads
|
3
.devcontainer/cbpi-dev-config/actor.json
Normal file
3
.devcontainer/cbpi-dev-config/actor.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"data": []
|
||||
}
|
6
.devcontainer/cbpi-dev-config/chromium.desktop
Normal file
6
.devcontainer/cbpi-dev-config/chromium.desktop
Normal 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"
|
335
.devcontainer/cbpi-dev-config/config.json
Normal file
335
.devcontainer/cbpi-dev-config/config.json
Normal 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"
|
||||
}
|
||||
}
|
20
.devcontainer/cbpi-dev-config/config.yaml
Normal file
20
.devcontainer/cbpi-dev-config/config.yaml
Normal 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
|
||||
|
9
.devcontainer/cbpi-dev-config/craftbeerpi.service
Normal file
9
.devcontainer/cbpi-dev-config/craftbeerpi.service
Normal file
|
@ -0,0 +1,9 @@
|
|||
[Unit]
|
||||
Description=Craftbeer Pi
|
||||
|
||||
[Service]
|
||||
WorkingDirectory=/home/pi
|
||||
ExecStart=/usr/local/bin/cbpi start
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"elements": []
|
||||
}
|
3
.devcontainer/cbpi-dev-config/fermenter_data.json
Normal file
3
.devcontainer/cbpi-dev-config/fermenter_data.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"data": []
|
||||
}
|
3
.devcontainer/cbpi-dev-config/kettle.json
Normal file
3
.devcontainer/cbpi-dev-config/kettle.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"data": []
|
||||
}
|
3
.devcontainer/cbpi-dev-config/sensor.json
Normal file
3
.devcontainer/cbpi-dev-config/sensor.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"data": []
|
||||
}
|
6
.devcontainer/cbpi-dev-config/step_data.json
Normal file
6
.devcontainer/cbpi-dev-config/step_data.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"basic": {
|
||||
"name": ""
|
||||
},
|
||||
"steps": []
|
||||
}
|
5
.devcontainer/createMqttUser.sh
Normal file
5
.devcontainer/createMqttUser.sh
Normal 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
|
51
.devcontainer/devcontainer.json
Normal file
51
.devcontainer/devcontainer.json
Normal 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"
|
||||
}
|
30
.devcontainer/docker-compose.dev.yml
Normal file
30
.devcontainer/docker-compose.dev.yml
Normal 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
|
10
.devcontainer/mosquitto/config/mosquitto.conf
Normal file
10
.devcontainer/mosquitto/config/mosquitto.conf
Normal 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
|
2
.devcontainer/mosquitto/config/mosquitto.passwd
Normal file
2
.devcontainer/mosquitto/config/mosquitto.passwd
Normal file
|
@ -0,0 +1,2 @@
|
|||
craftbeerpi:$7$101$cRIEIwJ9L/+TAFF1$lxT+v9SisokWaRBgB/Scut7DaotH4RMgzHttYHhwuy6m5yatSoac7bwrkztoQ7raNehBhKt/A4VVejnzozdxXA==
|
||||
mqtt-explorer:$7$101$SFFKvbIBVXFFAIBp$Pgue6DaAfcuhegjEqtTjf+WWgNZ8geiv1/3fXqmJ0APmd0L80wNTSrEhnFdJmHvi0/vW6V9bVKPJfVRDIjPxCw==
|
31
.devcontainer/mqtt-explorer/config/settings.json
Normal file
31
.devcontainer/mqtt-explorer/config/settings.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
7
.github/workflows/build.yml
vendored
7
.github/workflows/build.yml
vendored
|
@ -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
|
||||
|
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -14,6 +14,6 @@ node_modules
|
|||
.vscode
|
||||
.venv*
|
||||
.DS_Store
|
||||
.vscode/
|
||||
config/*
|
||||
logs/
|
||||
logs/
|
||||
.coverage
|
16
.vscode/launch.json
vendored
Normal file
16
.vscode/launch.json
vendored
Normal 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
8
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"python.pythonPath": "/bin/python3",
|
||||
"python.testing.pytestArgs": [
|
||||
"tests"
|
||||
],
|
||||
"python.testing.unittestEnabled": false,
|
||||
"python.testing.pytestEnabled": true
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
FROM alpine:latest as download
|
||||
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
|
||||
RUN curl https://github.com/craftbeerpi/craftbeerpi4-ui/archive/main.zip -L -o ./downloads/cbpi-ui.zip
|
||||
|
||||
FROM python:3.9 as base
|
||||
|
||||
|
|
14
README.md
14
README.md
|
@ -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)
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
__version__ = "4.0.4.a3"
|
||||
__version__ = "4.0.5.a10"
|
||||
__codename__ = "Spring Break"
|
||||
|
||||
|
|
|
@ -38,6 +38,12 @@ class CBPiBase(metaclass=ABCMeta):
|
|||
async def set_fermenter_target_temp(self,id, temp):
|
||||
await self.cbpi.fermenter.set_target_temp(id, temp)
|
||||
|
||||
def get_fermenter_target_pressure(self,id):
|
||||
return self.cbpi.fermenter._find_by_id(id).target_pressure
|
||||
|
||||
async def set_fermenter_target_pressure(self,id, temp):
|
||||
await self.cbpi.fermenter.set_target_pressure(id, temp)
|
||||
|
||||
def get_sensor(self,id):
|
||||
return self.cbpi.sensor.find_by_id(id)
|
||||
|
||||
|
|
|
@ -126,21 +126,23 @@ class Fermenter:
|
|||
id: str = None
|
||||
name: str = None
|
||||
sensor: Sensor = None
|
||||
pressure_sensor : Sensor = None
|
||||
heater: Actor = None
|
||||
cooler: Actor = None
|
||||
valve: Actor = None
|
||||
brewname: str = None
|
||||
description : str = None
|
||||
props: Props = Props()
|
||||
target_temp: float = 0
|
||||
target_pressure: float = 0
|
||||
type: str = None
|
||||
steps: List[Step]= field(default_factory=list)
|
||||
instance: str = None
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return "name={} props={} temp={}".format(self.name, self.props, self.target_temp)
|
||||
return "name={} props={} temp={}".format(self.name, self.props, self.target_temp, self.target_pressure)
|
||||
|
||||
# return "id={} name={} sensor={} heater={} cooler={} brewname={} props={} temp={} type={} steps={}".format(self.id, self.name, self.sensor, self.heater, self.cooler, self.brewname, self.props, self.target_temp, self.type, self.steps)
|
||||
def to_dict(self):
|
||||
|
||||
if self.instance is not None:
|
||||
|
@ -151,7 +153,7 @@ class Fermenter:
|
|||
state = False
|
||||
|
||||
steps = list(map(lambda item: item.to_dict(), self.steps))
|
||||
return dict(id=self.id, name=self.name, state=state, sensor=self.sensor, heater=self.heater, cooler=self.cooler, brewname=self.brewname, description=self.description, props=self.props.to_dict() if self.props is not None else None, target_temp=self.target_temp, type=self.type, steps=steps)
|
||||
return dict(id=self.id, name=self.name, state=state, sensor=self.sensor, pressure_sensor=self.pressure_sensor, heater=self.heater, cooler=self.cooler, valve=self.valve, brewname=self.brewname, description=self.description, props=self.props.to_dict() if self.props is not None else None, target_temp=self.target_temp, target_pressure=self.target_pressure, type=self.type, steps=steps)
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
|
@ -48,7 +48,7 @@ class CBPiExtension():
|
|||
|
||||
return data
|
||||
except:
|
||||
logger.warning("Faild to load config %s/config.yaml" % path)
|
||||
logger.warning("Failed to load config %s/config.yaml" % path)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ class CBPiSensor(CBPiBase, metaclass=ABCMeta):
|
|||
self.cbpi.push_update("cbpi/sensordata/{}".format(self.id), dict(id=self.id, value=value), retain=True)
|
||||
# self.cbpi.push_update("cbpi/sensor/{}/udpate".format(self.id), dict(id=self.id, value=value), retain=True)
|
||||
except:
|
||||
logging.error("Faild to push sensor update")
|
||||
logging.error("Failed to push sensor update")
|
||||
|
||||
async def start(self):
|
||||
pass
|
||||
|
|
586
cbpi/cli.py
586
cbpi/cli.py
|
@ -1,435 +1,285 @@
|
|||
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)
|
||||
|
||||
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)
|
||||
class CraftBeerPiCli():
|
||||
def __init__(self, config) -> None:
|
||||
self.config = config
|
||||
pass
|
||||
|
||||
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 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', "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)
|
||||
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', "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():
|
||||
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():
|
||||
print("List 1Wire")
|
||||
call(["modprobe", "w1-gpio"])
|
||||
call(["modprobe", "w1-therm"])
|
||||
try:
|
||||
for dirname in os.listdir('/sys/bus/w1/devices'):
|
||||
if (dirname.startswith("28") or dirname.startswith("10")):
|
||||
print(dirname)
|
||||
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():
|
||||
result = []
|
||||
print("")
|
||||
print(Fore.LIGHTYELLOW_EX,"List of active plugins", Style.RESET_ALL)
|
||||
print("")
|
||||
discovered_plugins = {
|
||||
name: importlib.import_module(name)
|
||||
for finder, name, ispkg
|
||||
in pkgutil.iter_modules()
|
||||
if name.startswith('cbpi') and len(name) > 4
|
||||
}
|
||||
for key, module in discovered_plugins.items():
|
||||
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(self):
|
||||
print("List 1Wire")
|
||||
call(["modprobe", "w1-gpio"])
|
||||
call(["modprobe", "w1-therm"])
|
||||
try:
|
||||
meta = metadata(key)
|
||||
result.append(dict(Name=meta["Name"], Version=meta["Version"], Author=meta["Author"], Homepage=meta["Home-page"], Summary=meta["Summary"]))
|
||||
|
||||
for dirname in os.listdir('/sys/bus/w1/devices'):
|
||||
if (dirname.startswith("28") or dirname.startswith("10")):
|
||||
print(dirname)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print(Fore.LIGHTGREEN_EX, tabulate(result, headers="keys"), Style.RESET_ALL)
|
||||
|
||||
|
||||
|
||||
def plugin_create():
|
||||
|
||||
print("Plugin Creation")
|
||||
print("")
|
||||
|
||||
questions = [
|
||||
{
|
||||
'type': 'input',
|
||||
'name': 'name',
|
||||
'message': 'Plugin Name:',
|
||||
def plugins_list(self):
|
||||
result = []
|
||||
print("")
|
||||
print(Fore.LIGHTYELLOW_EX,"List of active plugins", Style.RESET_ALL)
|
||||
print("")
|
||||
discovered_plugins = {
|
||||
name: importlib.import_module(name)
|
||||
for finder, name, ispkg
|
||||
in pkgutil.iter_modules()
|
||||
if name.startswith('cbpi') and len(name) > 4
|
||||
}
|
||||
]
|
||||
for key, module in discovered_plugins.items():
|
||||
try:
|
||||
meta = metadata(key)
|
||||
result.append(dict(Name=meta["Name"], Version=meta["Version"], Author=meta["Author"], Homepage=meta["Home-page"], Summary=meta["Summary"]))
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print(Fore.LIGHTGREEN_EX, tabulate(result, headers="keys"), Style.RESET_ALL)
|
||||
|
||||
answers = prompt(questions)
|
||||
|
||||
name = "cbpi4_" + answers["name"]
|
||||
if os.path.exists(os.path.join(".", name)) is True:
|
||||
print("Cant create Plugin. Folder {} already exists ".format(name))
|
||||
return
|
||||
def plugin_create(self):
|
||||
print("Plugin Creation")
|
||||
print("")
|
||||
|
||||
url = 'https://github.com/Manuel83/craftbeerpi4-plugin-template/archive/main.zip'
|
||||
r = requests.get(url)
|
||||
with open('temp.zip', 'wb') as f:
|
||||
f.write(r.content)
|
||||
questions = [
|
||||
{
|
||||
'type': 'input',
|
||||
'name': 'name',
|
||||
'message': 'Plugin Name:',
|
||||
}
|
||||
]
|
||||
|
||||
with ZipFile('temp.zip', 'r') as repo_zip:
|
||||
repo_zip.extractall()
|
||||
answers = prompt(questions)
|
||||
|
||||
os.rename("./craftbeerpi4-plugin-template-main", os.path.join(".", name))
|
||||
os.rename(os.path.join(".", name, "src"), os.path.join(".", name, name))
|
||||
name = "cbpi4_" + answers["name"]
|
||||
if os.path.exists(os.path.join(".", name)) is True:
|
||||
print("Cant create Plugin. Folder {} already exists ".format(name))
|
||||
return
|
||||
|
||||
import jinja2
|
||||
url = 'https://github.com/Manuel83/craftbeerpi4-plugin-template/archive/main.zip'
|
||||
r = requests.get(url)
|
||||
with open('temp.zip', 'wb') as f:
|
||||
f.write(r.content)
|
||||
|
||||
templateLoader = jinja2.FileSystemLoader(searchpath=os.path.join(".", name))
|
||||
templateEnv = jinja2.Environment(loader=templateLoader)
|
||||
TEMPLATE_FILE = "setup.py"
|
||||
template = templateEnv.get_template(TEMPLATE_FILE)
|
||||
outputText = template.render(name=name)
|
||||
with ZipFile('temp.zip', 'r') as repo_zip:
|
||||
repo_zip.extractall()
|
||||
|
||||
with open(os.path.join(".", name, "setup.py"), "w") as fh:
|
||||
fh.write(outputText)
|
||||
os.rename("./craftbeerpi4-plugin-template-main", os.path.join(".", name))
|
||||
os.rename(os.path.join(".", name, "src"), os.path.join(".", name, name))
|
||||
|
||||
TEMPLATE_FILE = "MANIFEST.in"
|
||||
template = templateEnv.get_template(TEMPLATE_FILE)
|
||||
outputText = template.render(name=name)
|
||||
with open(os.path.join(".", name, "MANIFEST.in"), "w") as fh:
|
||||
fh.write(outputText)
|
||||
import jinja2
|
||||
|
||||
TEMPLATE_FILE = os.path.join("/", name, "config.yaml")
|
||||
operatingsystem = str(platform.system()).lower()
|
||||
if operatingsystem.startswith("win"):
|
||||
TEMPLATE_FILE=str(TEMPLATE_FILE).replace('\\','/')
|
||||
|
||||
template = templateEnv.get_template(TEMPLATE_FILE)
|
||||
outputText = template.render(name=name)
|
||||
templateLoader = jinja2.FileSystemLoader(searchpath=os.path.join(".", name))
|
||||
templateEnv = jinja2.Environment(loader=templateLoader)
|
||||
TEMPLATE_FILE = "setup.py"
|
||||
template = templateEnv.get_template(TEMPLATE_FILE)
|
||||
outputText = template.render(name=name)
|
||||
|
||||
with open(os.path.join(".", name, name, "config.yaml"), "w") as fh:
|
||||
fh.write(outputText)
|
||||
with open(os.path.join(".", name, "setup.py"), "w") as fh:
|
||||
fh.write(outputText)
|
||||
|
||||
|
||||
print("")
|
||||
print("")
|
||||
print("Plugin {}{}{} created! ".format(Fore.LIGHTGREEN_EX, name, Style.RESET_ALL) )
|
||||
print("")
|
||||
print("Developer Documentation: https://openbrewing.gitbook.io/craftbeerpi4_support/readme/development")
|
||||
print("")
|
||||
print("Happy developing! Cheers")
|
||||
print("")
|
||||
print("")
|
||||
|
||||
TEMPLATE_FILE = "MANIFEST.in"
|
||||
template = templateEnv.get_template(TEMPLATE_FILE)
|
||||
outputText = template.render(name=name)
|
||||
with open(os.path.join(".", name, "MANIFEST.in"), "w") as fh:
|
||||
fh.write(outputText)
|
||||
|
||||
TEMPLATE_FILE = os.path.join("/", name, "config.yaml")
|
||||
operatingsystem = str(platform.system()).lower()
|
||||
if operatingsystem.startswith("win"):
|
||||
TEMPLATE_FILE=str(TEMPLATE_FILE).replace('\\','/')
|
||||
|
||||
template = templateEnv.get_template(TEMPLATE_FILE)
|
||||
outputText = template.render(name=name)
|
||||
|
||||
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) )
|
||||
print("")
|
||||
print("Developer Documentation: https://openbrewing.gitbook.io/craftbeerpi4_support/readme/development")
|
||||
print("")
|
||||
print("Happy developing! Cheers")
|
||||
print("")
|
||||
print("")
|
||||
|
||||
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))
|
||||
else:
|
||||
print("CraftBeerPi Autostart is {}OFF{}".format(Fore.RED,Style.RESET_ALL))
|
||||
elif(name == "on"):
|
||||
print("Add craftbeerpi.service to systemd")
|
||||
try:
|
||||
if os.path.exists(os.path.join("/etc/systemd/system","craftbeerpi.service")) is False:
|
||||
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")
|
||||
os.system('systemctl enable craftbeerpi.service')
|
||||
print('Enabled craftbeerpi service')
|
||||
os.system('systemctl start craftbeerpi.service')
|
||||
print('Started craftbeerpi.service')
|
||||
else:
|
||||
print("craftbeerpi.service is already located in /etc/systemd/system")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return
|
||||
return
|
||||
elif(name == "off"):
|
||||
print("Remove craftbeerpi.service from systemd")
|
||||
try:
|
||||
status = os.popen('systemctl list-units --type=service --state=running | grep craftbeerpi.service').read()
|
||||
if status.find("craftbeerpi.service") != -1:
|
||||
os.system('systemctl stop craftbeerpi.service')
|
||||
print('Stopped craftbeerpi service')
|
||||
os.system('systemctl disable craftbeerpi.service')
|
||||
print('Removed craftbeerpi.service as service')
|
||||
else:
|
||||
print('craftbeerpi.service service is not running')
|
||||
|
||||
if os.path.exists(os.path.join("/etc/systemd/system","craftbeerpi.service")) is True:
|
||||
os.remove(os.path.join("/etc/systemd/system","craftbeerpi.service"))
|
||||
print("Deleted craftbeerpi.service from /etc/systemd/system")
|
||||
else:
|
||||
print("craftbeerpi.service is not located in /etc/systemd/system")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return
|
||||
return
|
||||
|
||||
|
||||
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))
|
||||
else:
|
||||
print("CraftBeerPi Chromium Desktop is {}OFF{}".format(Fore.RED,Style.RESET_ALL))
|
||||
elif(name == "on"):
|
||||
print("Add chromium.desktop to /etc/xdg/autostart/")
|
||||
try:
|
||||
if os.path.exists(os.path.join("/etc/xdg/autostart/","chromium.desktop")) is False:
|
||||
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/")
|
||||
else:
|
||||
print("chromium.desktop is already located in /etc/xdg/autostart/")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return
|
||||
return
|
||||
elif(name == "off"):
|
||||
print("Remove chromium.desktop from /etc/xdg/autostart/")
|
||||
try:
|
||||
if os.path.exists(os.path.join("/etc/xdg/autostart/","chromium.desktop")) is True:
|
||||
os.remove(os.path.join("/etc/xdg/autostart/","chromium.desktop"))
|
||||
print("Deleted chromium.desktop from /etc/xdg/autostart/")
|
||||
else:
|
||||
print("chromium.desktop is not located in /etc/xdg/autostart/")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return
|
||||
return
|
||||
|
||||
|
||||
@click.group()
|
||||
def main():
|
||||
@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
|
||||
|
||||
|
||||
@click.command()
|
||||
def setup():
|
||||
@main.command()
|
||||
@click.pass_context
|
||||
def setup(context):
|
||||
'''Create Config folder'''
|
||||
print("Setting up CraftBeerPi")
|
||||
create_home_folder_structure()
|
||||
create_config_file()
|
||||
context.obj.setup()
|
||||
|
||||
|
||||
@click.command()
|
||||
@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(list, setup):
|
||||
def onewire(context, list, setup):
|
||||
'''Setup 1wire on Raspberry Pi'''
|
||||
if setup is True:
|
||||
setup_one_wire()
|
||||
context.obj.setup_one_wire()
|
||||
if list is True:
|
||||
list_one_wire()
|
||||
context.obj.list_one_wire()
|
||||
|
||||
@main.command()
|
||||
@click.pass_context
|
||||
def start(context):
|
||||
context.obj.start()
|
||||
|
||||
|
||||
@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():
|
||||
@main.command()
|
||||
@click.pass_context
|
||||
def plugins(context):
|
||||
'''List active plugins'''
|
||||
plugins_list()
|
||||
return
|
||||
|
||||
|
||||
|
||||
context.obj.plugins_list()
|
||||
|
||||
@click.command()
|
||||
def create():
|
||||
@click.pass_context
|
||||
def create(context):
|
||||
'''Create New Plugin'''
|
||||
plugin_create()
|
||||
context.obj.plugin_create()
|
||||
|
||||
@click.command()
|
||||
@main.command()
|
||||
@click.pass_context
|
||||
@click.argument('name')
|
||||
def autostart(name):
|
||||
def autostart(context, name):
|
||||
'''(on|off|status) 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))
|
||||
else:
|
||||
print("CraftBeerPi Autostart is {}OFF{}".format(Fore.RED,Style.RESET_ALL))
|
||||
elif(name == "on"):
|
||||
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")
|
||||
destfile = os.path.join("/etc/systemd/system")
|
||||
shutil.copy(srcfile, destfile)
|
||||
print("Copied craftbeerpi.service to /etc/systemd/system")
|
||||
os.system('systemctl enable craftbeerpi.service')
|
||||
print('Enabled craftbeerpi service')
|
||||
os.system('systemctl start craftbeerpi.service')
|
||||
print('Started craftbeerpi.service')
|
||||
else:
|
||||
print("craftbeerpi.service is already located in /etc/systemd/system")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return
|
||||
return
|
||||
elif(name == "off"):
|
||||
print("Remove craftbeerpi.service from systemd")
|
||||
try:
|
||||
status = os.popen('systemctl list-units --type=service --state=running | grep craftbeerpi.service').read()
|
||||
if status.find("craftbeerpi.service") != -1:
|
||||
os.system('systemctl stop craftbeerpi.service')
|
||||
print('Stopped craftbeerpi service')
|
||||
os.system('systemctl disable craftbeerpi.service')
|
||||
print('Removed craftbeerpi.service as service')
|
||||
else:
|
||||
print('craftbeerpi.service service is not running')
|
||||
|
||||
if os.path.exists(os.path.join("/etc/systemd/system","craftbeerpi.service")) is True:
|
||||
os.remove(os.path.join("/etc/systemd/system","craftbeerpi.service"))
|
||||
print("Deleted craftbeerpi.service from /etc/systemd/system")
|
||||
else:
|
||||
print("craftbeerpi.service is not located in /etc/systemd/system")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return
|
||||
return
|
||||
|
||||
|
||||
context.obj.autostart(name)
|
||||
|
||||
|
||||
@click.command()
|
||||
@main.command()
|
||||
@click.pass_context
|
||||
@click.argument('name')
|
||||
def chromium(name):
|
||||
def chromium(context, name):
|
||||
'''(on|off|status) Enable or disable Kiosk mode'''
|
||||
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))
|
||||
else:
|
||||
print("CraftBeerPi Chromium Desktop is {}OFF{}".format(Fore.RED,Style.RESET_ALL))
|
||||
elif(name == "on"):
|
||||
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")
|
||||
destfile = os.path.join("/etc/xdg/autostart/")
|
||||
shutil.copy(srcfile, destfile)
|
||||
print("Copied chromium.desktop to /etc/xdg/autostart/")
|
||||
else:
|
||||
print("chromium.desktop is already located in /etc/xdg/autostart/")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return
|
||||
return
|
||||
elif(name == "off"):
|
||||
print("Remove chromium.desktop from /etc/xdg/autostart/")
|
||||
try:
|
||||
if os.path.exists(os.path.join("/etc/xdg/autostart/","chromium.desktop")) is True:
|
||||
os.remove(os.path.join("/etc/xdg/autostart/","chromium.desktop"))
|
||||
print("Deleted chromium.desktop from /etc/xdg/autostart/")
|
||||
else:
|
||||
print("chromium.desktop is not located in /etc/xdg/autostart/")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
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)
|
||||
context.obj.chromium(name)
|
||||
|
|
133
cbpi/configFolder.py
Normal file
133
cbpi/configFolder.py
Normal file
|
@ -0,0 +1,133 @@
|
|||
import os
|
||||
from os import listdir
|
||||
from os.path import isfile, join
|
||||
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')
|
||||
fermenter_recipe_ids = [os.path.splitext(f)[0] for f in listdir(fermenter_recipes_folder) if isfile(join(fermenter_recipes_folder, f)) and f.endswith(".yaml")]
|
||||
return fermenter_recipe_ids
|
||||
|
||||
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)
|
|
@ -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):
|
||||
|
@ -36,6 +36,7 @@ class BasicController:
|
|||
logging.info("{} Load ".format(self.name))
|
||||
with open(self.path) as json_file:
|
||||
data = json.load(json_file)
|
||||
data['data'].sort(key=lambda x: x.get('name').upper())
|
||||
|
||||
for i in data["data"]:
|
||||
self.data.append(self.create(i))
|
||||
|
@ -151,4 +152,4 @@ class BasicController:
|
|||
item = self.find_by_id(id)
|
||||
await item.instance.__getattribute__(action)(**parameter)
|
||||
except Exception as e:
|
||||
logging.error("{} Faild to call action on {} {} {}".format(self.name, id, action, e))
|
||||
logging.error("{} Failed to call action on {} {} {}".format(self.name, id, action, e))
|
||||
|
|
|
@ -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):
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,18 +35,18 @@ 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):
|
||||
async def shutdown(self, app=None, fermenterid=None):
|
||||
self.save()
|
||||
if (fermenterid == None):
|
||||
for fermenter in self.data:
|
||||
|
@ -114,14 +114,17 @@ class FermentationController:
|
|||
id = data.get("id")
|
||||
name = data.get("name")
|
||||
sensor = data.get("sensor")
|
||||
pressure_sensor = data.get("pressure_sensor")
|
||||
heater = data.get("heater")
|
||||
cooler = data.get("cooler")
|
||||
valve = data.get("valve","")
|
||||
logictype = data.get("type")
|
||||
temp = data.get("target_temp")
|
||||
pressure = data.get("target_pressure")
|
||||
brewname = data.get("brewname")
|
||||
description = data.get("description")
|
||||
props = Props(data.get("props", {}))
|
||||
fermenter = Fermenter(id, name, sensor, heater, cooler, brewname, description, props, temp, logictype)
|
||||
fermenter = Fermenter(id, name, sensor, pressure_sensor, heater, cooler, valve, brewname, description, props, temp, pressure, logictype)
|
||||
fermenter.steps = list(map(lambda item: self._create_step(fermenter, item), data.get("steps", [])))
|
||||
self.push_update()
|
||||
return fermenter
|
||||
|
@ -130,7 +133,7 @@ class FermentationController:
|
|||
|
||||
|
||||
def _find_by_id(self, id):
|
||||
return next((item for item in self.data if item.id == id), None)
|
||||
return next((item for item in self.data if item.id == id), None)
|
||||
|
||||
async def get_all(self):
|
||||
return list(map(lambda x: x.to_dict(), self.data))
|
||||
|
@ -197,13 +200,16 @@ class FermentationController:
|
|||
def _update(old_item: Fermenter, item: Fermenter):
|
||||
old_item.name = item.name
|
||||
old_item.sensor = item.sensor
|
||||
old_item.pressure_sensor = item.pressure_sensor
|
||||
old_item.heater = item.heater
|
||||
old_item.cooler = item.cooler
|
||||
old_item.valve = item.valve
|
||||
old_item.type = item.type
|
||||
old_item.brewname = item.brewname
|
||||
old_item.description = item.description
|
||||
old_item.props = item.props
|
||||
old_item.target_temp = item.target_temp
|
||||
old_item.target_pressure = item.target_pressure
|
||||
return old_item
|
||||
|
||||
self.data = list(map(lambda old: _update(old, item) if old.id == item.id else old, self.data))
|
||||
|
@ -222,6 +228,17 @@ class FermentationController:
|
|||
except Exception as e:
|
||||
logging.error("Failed to set Target Temp {} {}".format(id, e))
|
||||
|
||||
async def set_target_pressure(self, id: str, target_pressure):
|
||||
try:
|
||||
item = self._find_by_id(id)
|
||||
logging.info(item.target_pressure)
|
||||
if item:
|
||||
item.target_pressure = target_pressure
|
||||
self.save()
|
||||
self.push_update()
|
||||
except Exception as e:
|
||||
logging.error("Failed to set Target Pressure {} {}".format(id, e))
|
||||
|
||||
async def delete(self, id: str ):
|
||||
item = self._find_by_id(id)
|
||||
self.data = list(filter(lambda item: item.id != id, self.data))
|
||||
|
@ -525,7 +542,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
|
||||
|
|
|
@ -29,56 +29,52 @@ 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_recipe_ids = 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 recipe_id in fermenter_recipe_ids:
|
||||
|
||||
with open(self.cbpi.config_folder.get_fermenter_recipe_by_id(recipe_id)) as file:
|
||||
data = yaml.load(file, Loader=yaml.FullLoader)
|
||||
dataset = data["basic"]
|
||||
dataset["file"] = filename
|
||||
dataset["file"] = recipe_id
|
||||
result.append(dataset)
|
||||
logging.info(result)
|
||||
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
|
||||
|
|
|
@ -22,7 +22,7 @@ class NotificationController:
|
|||
try:
|
||||
del self.listener[listener_id]
|
||||
except:
|
||||
self.logger.error("Faild to remove listener {}".format(listener_id))
|
||||
self.logger.error("Failed to remove listener {}".format(listener_id))
|
||||
|
||||
async def _call_listener(self, title, message, type, action):
|
||||
for id, method in self.listener.items():
|
||||
|
@ -60,5 +60,5 @@ class NotificationController:
|
|||
asyncio.create_task(action.method())
|
||||
del self.callback_cache[notification_id]
|
||||
except Exception as e:
|
||||
self.logger.error("Faild to call notificatoin callback")
|
||||
self.logger.error("Failed to call notificatoin callback")
|
||||
|
|
@ -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
|
||||
|
|
|
@ -12,7 +12,7 @@ class SensorController(BasicController):
|
|||
instance = data.get("instance")
|
||||
state =instance.get_state()
|
||||
except Exception as e:
|
||||
logging.error("Faild to create sensor dict {} ".format(e))
|
||||
logging.error("Failed to create sensor dict {} ".format(e))
|
||||
state = dict()
|
||||
|
||||
return dict(name=data.get("name"), id=data.get("id"), type=data.get("type"), state=state,props=data.get("props", []))
|
||||
|
@ -21,5 +21,5 @@ class SensorController(BasicController):
|
|||
try:
|
||||
return self.find_by_id(id).instance.get_state()
|
||||
except Exception as e:
|
||||
logging.error("Faild read sensor value {} {} ".format(id, e))
|
||||
logging.error("Failed read sensor value {} {} ".format(id, e))
|
||||
return None
|
|
@ -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
|
||||
|
@ -230,7 +230,7 @@ class StepController:
|
|||
await self.save()
|
||||
|
||||
async def shutdown(self, app=None):
|
||||
logging.info("Mash Profile Shutdonw")
|
||||
logging.info("Mash Profile Shutdown")
|
||||
for p in self.profile:
|
||||
instance = p.instance
|
||||
# Stopping all running task
|
||||
|
@ -279,7 +279,7 @@ class StepController:
|
|||
await step.instance.start()
|
||||
step.status = StepState.ACTIVE
|
||||
except Exception as e:
|
||||
logging.error("Faild to start step %s" % step)
|
||||
logging.error("Failed to start step %s" % step)
|
||||
|
||||
async def save_basic(self, data):
|
||||
logging.info("SAVE Basic Data")
|
||||
|
@ -293,7 +293,7 @@ class StepController:
|
|||
item = self.find_by_id(id)
|
||||
await item.instance.__getattribute__(action)(**parameter)
|
||||
except Exception as e:
|
||||
logging.error("Step Controller -Faild to call action on {} {} {}".format(id, action, e))
|
||||
logging.error("Step Controller -Failed to call action on {} {} {}".format(id, action, e))
|
||||
|
||||
async def load_recipe(self, data):
|
||||
try:
|
||||
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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'):
|
||||
|
@ -96,8 +96,9 @@ 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")
|
||||
|
||||
|
@ -288,6 +289,7 @@ class CraftBeerPi:
|
|||
self._setup_http_index()
|
||||
self.plugin.load_plugins()
|
||||
self.plugin.load_plugins_from_evn()
|
||||
await self.fermenter.init()
|
||||
await self.sensor.init()
|
||||
await self.step.init()
|
||||
|
||||
|
@ -295,8 +297,8 @@ class CraftBeerPi:
|
|||
await self.kettle.init()
|
||||
await self.call_initializer(self.app)
|
||||
await self.dashboard.init()
|
||||
await self.fermenter.init()
|
||||
|
||||
|
||||
|
||||
self._swagger_setup()
|
||||
|
||||
level = logging.INFO
|
||||
|
|
|
@ -46,7 +46,7 @@ class ConfigUpdate(CBPiExtension):
|
|||
influxdbpwd = self.cbpi.config.get("INFLUXDBPWD", None)
|
||||
influxdbcloud = self.cbpi.config.get("INFLUXDBCLOUD", None)
|
||||
mqttupdate = self.cbpi.config.get("MQTTUpdate", None)
|
||||
|
||||
PRESSURE_UNIT = self.cbpi.config.get("PRESSURE_UNIT", None)
|
||||
|
||||
|
||||
if boil_temp is None:
|
||||
|
@ -276,6 +276,15 @@ class ConfigUpdate(CBPiExtension):
|
|||
except:
|
||||
logger.warning('Unable to update database')
|
||||
|
||||
## Check if PRESSURE_UNIT is in config
|
||||
if PRESSURE_UNIT is None:
|
||||
logger.info("INIT PRESSURE_UNIT")
|
||||
try:
|
||||
await self.cbpi.config.add("PRESSURE_UNIT", "kPa", ConfigType.SELECT, "Set unit for pressure",
|
||||
[{"label": "kPa", "value": "kPa"},
|
||||
{"label": "PSI", "value": "PSI"}])
|
||||
except:
|
||||
logger.warning('Unable to update config')
|
||||
|
||||
def setup(cbpi):
|
||||
cbpi.plugin.register("ConfigUpdate", ConfigUpdate)
|
||||
|
|
|
@ -94,6 +94,7 @@ class FermenterTargetTempStep(CBPiFermentationStep):
|
|||
self.AutoMode = True if self.props.get("AutoMode","No") == "Yes" else False
|
||||
if self.fermenter is not None:
|
||||
self.fermenter.target_temp = float(self.props.get("Temp", 0))
|
||||
self.fermenter.target_pressure = 0
|
||||
if self.AutoMode == True:
|
||||
await self.setAutoMode(True)
|
||||
self.summary = "Waiting for Target Temp"
|
||||
|
@ -150,8 +151,9 @@ class FermenterTargetTempStep(CBPiFermentationStep):
|
|||
@parameters([Property.Number(label="TimerD", description="Timer Days", configurable=True),
|
||||
Property.Number(label="TimerH", description="Timer Hours", configurable=True),
|
||||
Property.Number(label="TimerM", description="Timer Minutes", configurable=True),
|
||||
Property.Number(label="Temp", configurable=True),
|
||||
Property.Sensor(label="Sensor"),
|
||||
Property.Number(label="Temp", configurable=True, description="Step Temperature"),
|
||||
Property.Number(label="Pressure", configurable=True, description="Step Pressure"),
|
||||
Property.Sensor(label="Sensor", description="Temperature Sensor"),
|
||||
Property.Select(label="AutoMode",options=["Yes","No"], description="Switch Fermenterlogic automatically on and off -> Yes")])
|
||||
class FermenterStep(CBPiFermentationStep):
|
||||
|
||||
|
@ -206,6 +208,7 @@ class FermenterStep(CBPiFermentationStep):
|
|||
self.AutoMode = True if self.props.get("AutoMode", "No") == "Yes" else False
|
||||
if self.fermenter is not None:
|
||||
self.fermenter.target_temp = float(self.props.get("Temp", 0))
|
||||
self.fermenter.target_pressure = float(self.props.get("Pressure", 0))
|
||||
if self.AutoMode == True:
|
||||
await self.setAutoMode(True)
|
||||
await self.push_update()
|
||||
|
@ -293,8 +296,9 @@ class FermenterStep(CBPiFermentationStep):
|
|||
logging.error("Failed to switch on FermenterLogic {} {}".format(self.fermenter.id, e))
|
||||
|
||||
@parameters([Property.Number(label="Temp", configurable=True, description = "Ramp to this temp"),
|
||||
Property.Number(label="Pressure", configurable=True, description="Step Pressure"),
|
||||
Property.Number(label="RampRate", configurable=True, description = "Ramp x °C/F per day. Default: 1"),
|
||||
Property.Sensor(label="Sensor"),
|
||||
Property.Sensor(label="Sensor", description="Temperature Sensor"),
|
||||
Property.Text(label="Notification",configurable = True, description = "Text for notification when Temp is reached"),
|
||||
Property.Select(label="AutoMode",options=["Yes","No"], description="Switch Fermenterlogic automatically on and off -> Yes")])
|
||||
class FermenterRampTempStep(CBPiFermentationStep):
|
||||
|
@ -324,6 +328,7 @@ class FermenterRampTempStep(CBPiFermentationStep):
|
|||
logging.info(self.rate)
|
||||
self.target_temp = round(float(self.props.get("Temp", 0))*10)/10
|
||||
logging.info(self.target_temp)
|
||||
self.fermenter.target_pressure = float(self.props.get("Pressure", 0))
|
||||
while self.get_sensor_value(self.props.get("Sensor", None)).get("value") > 900:
|
||||
await asyncio.sleep(1)
|
||||
self.starttemp = self.get_sensor_value(self.props.get("Sensor", None)).get("value")
|
||||
|
|
|
@ -100,7 +100,7 @@ class FermenterHysteresis(CBPiFermenterLogic):
|
|||
except asyncio.CancelledError as e:
|
||||
pass
|
||||
except Exception as e:
|
||||
logging.error("CustomLogic Error {}".format(e))
|
||||
logging.error("Fermenter Hysteresis Error {}".format(e))
|
||||
finally:
|
||||
self.running = False
|
||||
if self.heater:
|
||||
|
@ -109,6 +109,111 @@ class FermenterHysteresis(CBPiFermenterLogic):
|
|||
await self.actor_off(self.cooler)
|
||||
|
||||
|
||||
@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="SpundingOffsetOpen", configurable=True, description="Offset above target pressure as decimal number when the valve is opened"),
|
||||
Property.Select(label="ValveRelease", options=[1,2,3,4,5],description="Valve Release time in seconds"),
|
||||
Property.Select(label="Pause", options=[1,2,3,4,5],description="Pause time in seconds between valve release"),
|
||||
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)")])
|
||||
|
||||
class FermenterSpundingHysteresis(CBPiFermenterLogic):
|
||||
# subroutine that controls pressure
|
||||
async def pressure_control(self):
|
||||
self.spunding_offset=float(self.props.get("SpundingOffsetOpen",0))
|
||||
self.valverelease=int(self.props.get("ValveRelease",1))
|
||||
self.pause=int(self.props.get("Pause",2))
|
||||
if self.valve and self.fermenter.pressure_sensor:
|
||||
#valve = self.cbpi.actor.find_by_id(self.valve)
|
||||
|
||||
await self.actor_off(self.valve)
|
||||
#logging.info("Closing Spunding Valve")
|
||||
|
||||
while self.running:
|
||||
target_pressure=float(self.fermenter.target_pressure)
|
||||
current_pressure = float(self.get_sensor_value(self.fermenter.pressure_sensor).get("value"))
|
||||
#logging.info(f'Target: {target_pressure} | Current: {current_pressure}')
|
||||
if current_pressure >= (target_pressure + self.spunding_offset) and target_pressure !=0:
|
||||
while current_pressure >= target_pressure:
|
||||
await self.actor_on(self.valve)
|
||||
await asyncio.sleep(self.valverelease)
|
||||
await self.actor_off(self.valve)
|
||||
await asyncio.sleep(self.pause)
|
||||
current_pressure = float(self.get_sensor_value(self.fermenter.pressure_sensor).get("value"))
|
||||
#logging.info("Value higher than target: Spunding loop is running")
|
||||
|
||||
await asyncio.sleep(1)
|
||||
else:
|
||||
logging.info("No valve or pressure sensor defined")
|
||||
|
||||
async def temperature_control(self):
|
||||
self.heater_offset_min = float(self.props.get("HeaterOffsetOn", 0))
|
||||
self.heater_offset_max = float(self.props.get("HeaterOffsetOff", 0))
|
||||
self.cooler_offset_min = float(self.props.get("CoolerOffsetOn", 0))
|
||||
self.cooler_offset_max = float(self.props.get("CoolerOffsetOff", 0))
|
||||
|
||||
heater = self.cbpi.actor.find_by_id(self.heater)
|
||||
cooler = self.cbpi.actor.find_by_id(self.cooler)
|
||||
|
||||
while self.running == True:
|
||||
|
||||
sensor_value = float(self.get_sensor_value(self.fermenter.sensor).get("value"))
|
||||
target_temp = float(self.get_fermenter_target_temp(self.id))
|
||||
|
||||
try:
|
||||
heater_state = heater.instance.state
|
||||
except:
|
||||
heater_state= False
|
||||
try:
|
||||
cooler_state = cooler.instance.state
|
||||
except:
|
||||
cooler_state= False
|
||||
|
||||
if sensor_value + self.heater_offset_min <= target_temp:
|
||||
if self.heater and (heater_state == False):
|
||||
await self.actor_on(self.heater)
|
||||
|
||||
if sensor_value + self.heater_offset_max >= target_temp:
|
||||
if self.heater and (heater_state == True):
|
||||
await self.actor_off(self.heater)
|
||||
|
||||
if sensor_value >= self.cooler_offset_min + target_temp:
|
||||
if self.cooler and (cooler_state == False):
|
||||
await self.actor_on(self.cooler)
|
||||
|
||||
if sensor_value <= self.cooler_offset_max + target_temp:
|
||||
if self.cooler and (cooler_state == True):
|
||||
await self.actor_off(self.cooler)
|
||||
|
||||
await asyncio.sleep(1)
|
||||
|
||||
async def run(self):
|
||||
try:
|
||||
self.fermenter = self.get_fermenter(self.id)
|
||||
self.heater = self.fermenter.heater
|
||||
self.cooler = self.fermenter.cooler
|
||||
self.valve = self.fermenter.valve
|
||||
|
||||
pressure_controller = asyncio.create_task(self.pressure_control())
|
||||
temperature_controller = asyncio.create_task(self.temperature_control())
|
||||
|
||||
await pressure_controller
|
||||
await temperature_controller
|
||||
|
||||
except asyncio.CancelledError as e:
|
||||
pass
|
||||
except Exception as e:
|
||||
logging.error("Fermenter Spunding Hysteresis Error {}".format(e))
|
||||
finally:
|
||||
self.running = False
|
||||
if self.heater:
|
||||
await self.actor_off(self.heater)
|
||||
if self.cooler:
|
||||
await self.actor_off(self.cooler)
|
||||
if self.valve:
|
||||
await self.actor_off(self.valve)
|
||||
|
||||
def setup(cbpi):
|
||||
|
||||
|
@ -119,7 +224,7 @@ def setup(cbpi):
|
|||
:param cbpi: the cbpi core
|
||||
:return:
|
||||
'''
|
||||
|
||||
cbpi.plugin.register("Fermenter Spunding Hysteresis", FermenterSpundingHysteresis)
|
||||
cbpi.plugin.register("Fermenter Hysteresis", FermenterHysteresis)
|
||||
cbpi.plugin.register("Fermenter Autostart", FermenterAutostart)
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import asyncio
|
||||
import random
|
||||
|
||||
from cbpi.api import parameters, CBPiSensor
|
||||
|
||||
import logging
|
||||
from cbpi.api import *
|
||||
from cbpi.api.base import CBPiBase
|
||||
from cbpi.api.dataclasses import Kettle, Props, Fermenter
|
||||
|
||||
@parameters([])
|
||||
class CustomSensor(CBPiSensor):
|
||||
|
@ -23,6 +24,44 @@ class CustomSensor(CBPiSensor):
|
|||
def get_state(self):
|
||||
return dict(value=self.value)
|
||||
|
||||
@parameters([Property.Number(label="Pressure", configurable=True, description="Start Pressure"),
|
||||
Property.Number(label="PressureIncrease", configurable=True, description="Pressure increase per hour"),
|
||||
Property.Number(label="PressureDecrease", configurable=True, description="Pressure decrease per second on openm valve"),
|
||||
Property.Fermenter(label="Fermenter",description="Fermenter")])
|
||||
class DummyPressure(CBPiSensor):
|
||||
|
||||
def __init__(self, cbpi, id, props):
|
||||
super(DummyPressure, self).__init__(cbpi, id, props)
|
||||
self.value = float(self.props.get("Pressure",0))
|
||||
fermenter=self.props.get("Fermenter",None)
|
||||
self.fermenter=self.get_fermenter(fermenter)
|
||||
self.valve=self.fermenter.valve
|
||||
|
||||
async def run(self):
|
||||
self.uprate=float(self.props.get("PressureIncrease",0))/3600
|
||||
self.decrease=float(self.props.get("PressureDecrease",0))
|
||||
logging.info(self.uprate)
|
||||
logging.info(self.decrease)
|
||||
|
||||
while self.running:
|
||||
valve_state=self.get_actor_state(self.valve)
|
||||
fermenter_instance=self.fermenter.instance
|
||||
if fermenter_instance:
|
||||
fermenter_state=fermenter_instance.state
|
||||
else:
|
||||
fermenter_state = False
|
||||
if valve_state == False and fermenter_state:
|
||||
self.value = self.value + self.uprate
|
||||
elif valve_state and fermenter_state:
|
||||
self.value=self.value-self.decrease
|
||||
|
||||
self.log_data(self.value)
|
||||
|
||||
self.push_update(round(self.value,2))
|
||||
await asyncio.sleep(1)
|
||||
|
||||
def get_state(self):
|
||||
return dict(value=self.value)
|
||||
|
||||
def setup(cbpi):
|
||||
'''
|
||||
|
@ -33,3 +72,4 @@ def setup(cbpi):
|
|||
:return:
|
||||
'''
|
||||
cbpi.plugin.register("CustomSensor", CustomSensor)
|
||||
cbpi.plugin.register("DummyPressure", DummyPressure)
|
||||
|
|
|
@ -23,6 +23,32 @@ class ActorHttpEndpoints():
|
|||
description: successful operation
|
||||
"""
|
||||
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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -77,7 +77,9 @@ class FermentationHttpEndpoints():
|
|||
description: successful operation
|
||||
"""
|
||||
data = await request.json()
|
||||
fermenter = Fermenter(id=id, name=data.get("name"), sensor=data.get("sensor"), heater=data.get("heater"), cooler=data.get("cooler"), brewname=data.get("brewname"), description=data.get("description"), target_temp=data.get("target_temp"), props=Props(data.get("props", {})), type=data.get("type"))
|
||||
fermenter = Fermenter(id=id, name=data.get("name"), sensor=data.get("sensor"), pressure_sensor=data.get("pressure_sensor"), heater=data.get("heater"),
|
||||
cooler=data.get("cooler"), valve=data.get("valve"), brewname=data.get("brewname"), description=data.get("description"),
|
||||
target_temp=data.get("target_temp"), target_pressure=data.get("target_pressure"), props=Props(data.get("props", {})), type=data.get("type"))
|
||||
response_data = await self.controller.create(fermenter)
|
||||
return web.json_response(data=response_data.to_dict())
|
||||
|
||||
|
@ -115,7 +117,9 @@ class FermentationHttpEndpoints():
|
|||
"""
|
||||
id = request.match_info['id']
|
||||
data = await request.json()
|
||||
fermenter = Fermenter(id=id, name=data.get("name"), sensor=data.get("sensor"), heater=data.get("heater"), cooler=data.get("cooler"), brewname=data.get("brewname"), description=data.get("description"), target_temp=data.get("target_temp"), props=Props(data.get("props", {})), type=data.get("type"))
|
||||
fermenter = Fermenter(id=id, name=data.get("name"), sensor=data.get("sensor"), pressure_sensor=data.get("pressure_sensor"), heater=data.get("heater"),
|
||||
cooler=data.get("cooler"), valve=data.get("valve"), brewname=data.get("brewname"), description=data.get("description"),
|
||||
target_temp=data.get("target_temp"), target_pressure=data.get("target_pressure"), props=Props(data.get("props", {})), type=data.get("type"))
|
||||
return web.json_response(data=(await self.controller.update(fermenter)).to_dict())
|
||||
|
||||
@request_mapping(path="/{id}", method="DELETE", auth_required=False)
|
||||
|
@ -284,6 +288,40 @@ class FermentationHttpEndpoints():
|
|||
await self.controller.set_target_temp(id,data.get("temp"))
|
||||
return web.Response(status=204)
|
||||
|
||||
@request_mapping(path="/{id}/target_pressure", method="POST", auth_required=auth)
|
||||
async def http_target_pressure(self, request) -> web.Response:
|
||||
"""
|
||||
|
||||
---
|
||||
description: Set Target pressure for Fermenter
|
||||
tags:
|
||||
- Fermenter
|
||||
parameters:
|
||||
- name: "id"
|
||||
in: "path"
|
||||
description: "Fermenter ID"
|
||||
required: true
|
||||
type: "integer"
|
||||
format: "int64"
|
||||
- in: body
|
||||
name: body
|
||||
description: Update Pressure
|
||||
required: true
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
temp:
|
||||
type: integer
|
||||
responses:
|
||||
"204":
|
||||
description: successful operation
|
||||
"""
|
||||
id = request.match_info['id']
|
||||
data = await request.json()
|
||||
await self.controller.set_target_pressure(id,data.get("pressure"))
|
||||
return web.Response(status=204)
|
||||
|
||||
|
||||
@request_mapping(path="/{id}/addstep", method="POST", auth_required=False)
|
||||
async def http_add_step(self, request):
|
||||
|
||||
|
|
|
@ -33,6 +33,10 @@ class CBPiWebSocket:
|
|||
for ws in self._clients:
|
||||
async def send_data(ws, data):
|
||||
try:
|
||||
try:
|
||||
data['data'].sort(key=lambda x: x.get('name').upper())
|
||||
except:
|
||||
pass
|
||||
await ws.send_json(data=data, dumps=json_dumps)
|
||||
except Exception as e:
|
||||
self.logger.error("Error with client %s: %s" % (ws, str(e)))
|
||||
|
|
BIN
craftbeerpi.db
BIN
craftbeerpi.db
Binary file not shown.
|
@ -21,4 +21,6 @@ asyncio-mqtt
|
|||
psutil==5.9.0
|
||||
zipp>=0.5
|
||||
PyInquirer==1.0.3
|
||||
colorama==0.4.4
|
||||
colorama==0.4.4
|
||||
pytest-aiohttp
|
||||
coverage==6.3.1
|
||||
|
|
2
run.py
2
run.py
|
@ -1,3 +1,3 @@
|
|||
from cbpi.cli import main
|
||||
|
||||
main()
|
||||
main(auto_envvar_prefix='CBPI')
|
12
tests/cbpi-test-config/actor.json
Normal file
12
tests/cbpi-test-config/actor.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"data": [
|
||||
{
|
||||
"id": "3CUJte4bkxDMFCtLX8eqsX",
|
||||
"name": "SomeActor",
|
||||
"power": 100,
|
||||
"props": {},
|
||||
"state": false,
|
||||
"type": "DummyActor"
|
||||
}
|
||||
]
|
||||
}
|
6
tests/cbpi-test-config/chromium.desktop
Normal file
6
tests/cbpi-test-config/chromium.desktop
Normal 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"
|
148
tests/cbpi-test-config/config.json
Normal file
148
tests/cbpi-test-config/config.json
Normal 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"
|
||||
}
|
||||
}
|
20
tests/cbpi-test-config/config.yaml
Normal file
20
tests/cbpi-test-config/config.yaml
Normal 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
|
||||
|
9
tests/cbpi-test-config/craftbeerpi.service
Normal file
9
tests/cbpi-test-config/craftbeerpi.service
Normal file
|
@ -0,0 +1,9 @@
|
|||
[Unit]
|
||||
Description=Craftbeer Pi
|
||||
|
||||
[Service]
|
||||
WorkingDirectory=/home/pi
|
||||
ExecStart=/usr/local/bin/cbpi start
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
62
tests/cbpi-test-config/craftbeerpiboot
Normal file
62
tests/cbpi-test-config/craftbeerpiboot
Normal 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
|
3
tests/cbpi-test-config/dashboard/cbpi_dashboard_1.json
Normal file
3
tests/cbpi-test-config/dashboard/cbpi_dashboard_1.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"elements": []
|
||||
}
|
0
tests/cbpi-test-config/dashboard/widgets/.empty
Normal file
0
tests/cbpi-test-config/dashboard/widgets/.empty
Normal file
5
tests/cbpi-test-config/fermenter_data.json
Normal file
5
tests/cbpi-test-config/fermenter_data.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"data": [
|
||||
|
||||
]
|
||||
}
|
5
tests/cbpi-test-config/kettle.json
Normal file
5
tests/cbpi-test-config/kettle.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"data": [
|
||||
|
||||
]
|
||||
}
|
3
tests/cbpi-test-config/plugin_list.txt
Normal file
3
tests/cbpi-test-config/plugin_list.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
cbpi4-ui:
|
||||
installation_date: '2021-01-06 16:03:31'
|
||||
version: '0.0.1'
|
0
tests/cbpi-test-config/recipes/.empty
Normal file
0
tests/cbpi-test-config/recipes/.empty
Normal file
5
tests/cbpi-test-config/sensor.json
Normal file
5
tests/cbpi-test-config/sensor.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"data": [
|
||||
|
||||
]
|
||||
}
|
BIN
tests/cbpi-test-config/splash.png
Normal file
BIN
tests/cbpi-test-config/splash.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.9 MiB |
8
tests/cbpi-test-config/step_data.json
Normal file
8
tests/cbpi-test-config/step_data.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"basic": {
|
||||
"name": ""
|
||||
},
|
||||
"steps": [
|
||||
|
||||
]
|
||||
}
|
0
tests/cbpi-test-config/upload/.empty
Normal file
0
tests/cbpi-test-config/upload/.empty
Normal file
23
tests/cbpi_config_fixture.py
Normal file
23
tests/cbpi_config_fixture.py
Normal 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.
|
@ -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
|
||||
|
|
|
@ -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()
|
|
@ -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"
|
|
@ -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
|
||||
dashboard_id = await resp.json()
|
||||
|
||||
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
|
||||
|
||||
# Add dashboard content
|
||||
# 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)
|
||||
assert resp.status == 204
|
|
@ -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()
|
|
@ -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
|
||||
|
@ -35,15 +28,4 @@ class TestSwitch(unittest.TestCase):
|
|||
@patch("RPi.GPIO.output")
|
||||
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)
|
||||
|
||||
patched_output.assert_called_once_with(self.GPIO_NUM, GPIO.LOW)
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
@ -66,34 +49,4 @@ class StepTestCase(AioHTTPTestCase):
|
|||
done, pending = await asyncio.wait({future})
|
||||
|
||||
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())
|
||||
pass
|
|
@ -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()
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
||||
|
|
Loading…
Reference in a new issue