mirror of
https://github.com/PiBrewing/craftbeerpi4.git
synced 2024-12-01 11:14:15 +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
|
- name: Clean
|
||||||
run: python setup.py clean --all
|
run: python setup.py clean --all
|
||||||
|
|
||||||
# - name: Run tests
|
- name: Install Requirements
|
||||||
# run: python -m unittest tests
|
run: pip3 install -r requirements.txt
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: coverage run --source cbpi -m pytest tests
|
||||||
|
|
||||||
- name: Build source distribution package for CraftBeerPi
|
- name: Build source distribution package for CraftBeerPi
|
||||||
run: python setup.py sdist
|
run: python setup.py sdist
|
||||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -14,6 +14,6 @@ node_modules
|
||||||
.vscode
|
.vscode
|
||||||
.venv*
|
.venv*
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.vscode/
|
|
||||||
config/*
|
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
|
FROM alpine:latest as download
|
||||||
RUN apk --no-cache add curl && mkdir /downloads
|
RUN apk --no-cache add curl && mkdir /downloads
|
||||||
# Download installation files
|
# Download installation files
|
||||||
RUN curl https://github.com/avollkopf/craftbeerpi4-ui/archive/main.zip -L -o ./downloads/cbpi-ui.zip
|
RUN curl https://github.com/craftbeerpi/craftbeerpi4-ui/archive/main.zip -L -o ./downloads/cbpi-ui.zip
|
||||||
|
|
||||||
FROM python:3.9 as base
|
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.
|
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).
|
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
|
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)
|
[![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"
|
__codename__ = "Spring Break"
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,12 @@ class CBPiBase(metaclass=ABCMeta):
|
||||||
async def set_fermenter_target_temp(self,id, temp):
|
async def set_fermenter_target_temp(self,id, temp):
|
||||||
await self.cbpi.fermenter.set_target_temp(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):
|
def get_sensor(self,id):
|
||||||
return self.cbpi.sensor.find_by_id(id)
|
return self.cbpi.sensor.find_by_id(id)
|
||||||
|
|
||||||
|
|
|
@ -126,21 +126,23 @@ class Fermenter:
|
||||||
id: str = None
|
id: str = None
|
||||||
name: str = None
|
name: str = None
|
||||||
sensor: Sensor = None
|
sensor: Sensor = None
|
||||||
|
pressure_sensor : Sensor = None
|
||||||
heater: Actor = None
|
heater: Actor = None
|
||||||
cooler: Actor = None
|
cooler: Actor = None
|
||||||
|
valve: Actor = None
|
||||||
brewname: str = None
|
brewname: str = None
|
||||||
description : str = None
|
description : str = None
|
||||||
props: Props = Props()
|
props: Props = Props()
|
||||||
target_temp: float = 0
|
target_temp: float = 0
|
||||||
|
target_pressure: float = 0
|
||||||
type: str = None
|
type: str = None
|
||||||
steps: List[Step]= field(default_factory=list)
|
steps: List[Step]= field(default_factory=list)
|
||||||
instance: str = None
|
instance: str = None
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
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):
|
def to_dict(self):
|
||||||
|
|
||||||
if self.instance is not None:
|
if self.instance is not None:
|
||||||
|
@ -151,7 +153,7 @@ class Fermenter:
|
||||||
state = False
|
state = False
|
||||||
|
|
||||||
steps = list(map(lambda item: item.to_dict(), self.steps))
|
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
|
@dataclass
|
||||||
|
|
|
@ -48,7 +48,7 @@ class CBPiExtension():
|
||||||
|
|
||||||
return data
|
return data
|
||||||
except:
|
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/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)
|
# self.cbpi.push_update("cbpi/sensor/{}/udpate".format(self.id), dict(id=self.id, value=value), retain=True)
|
||||||
except:
|
except:
|
||||||
logging.error("Faild to push sensor update")
|
logging.error("Failed to push sensor update")
|
||||||
|
|
||||||
async def start(self):
|
async def start(self):
|
||||||
pass
|
pass
|
||||||
|
|
326
cbpi/cli.py
326
cbpi/cli.py
|
@ -1,103 +1,45 @@
|
||||||
import argparse
|
|
||||||
import datetime
|
|
||||||
import logging
|
import logging
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import re
|
|
||||||
import requests
|
import requests
|
||||||
import yaml
|
from cbpi.configFolder import ConfigFolder
|
||||||
from cbpi.utils.utils import load_config
|
from cbpi.utils.utils import load_config
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
from cbpi.craftbeerpi import CraftBeerPi
|
from cbpi.craftbeerpi import CraftBeerPi
|
||||||
import os
|
import os
|
||||||
import platform
|
|
||||||
import pathlib
|
|
||||||
import pkgutil
|
import pkgutil
|
||||||
import shutil
|
import shutil
|
||||||
import yaml
|
|
||||||
import click
|
import click
|
||||||
from subprocess import call
|
from subprocess import call
|
||||||
import zipfile
|
|
||||||
from colorama import Fore, Back, Style
|
from colorama import Fore, Back, Style
|
||||||
from importlib import import_module
|
|
||||||
import importlib
|
import importlib
|
||||||
from jinja2 import Template
|
from importlib_metadata import metadata
|
||||||
from importlib_metadata import metadata, version
|
|
||||||
from tabulate import tabulate
|
from tabulate import tabulate
|
||||||
from PyInquirer import prompt, print_json
|
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:
|
class CraftBeerPiCli():
|
||||||
srcfile = os.path.join(os.path.dirname(__file__), "config", "actor.json")
|
def __init__(self, config) -> None:
|
||||||
destfile = os.path.join(".", 'config')
|
self.config = config
|
||||||
shutil.copy(srcfile, destfile)
|
pass
|
||||||
|
|
||||||
if os.path.exists(os.path.join(".", 'config', "sensor.json")) is False:
|
def setup(self):
|
||||||
srcfile = os.path.join(os.path.dirname(__file__), "config", "sensor.json")
|
print("Setting up CraftBeerPi")
|
||||||
destfile = os.path.join(".", 'config')
|
self.config.create_home_folder_structure()
|
||||||
shutil.copy(srcfile, destfile)
|
self.config.create_config_file()
|
||||||
|
|
||||||
if os.path.exists(os.path.join(".", 'config', "kettle.json")) is False:
|
def start(self):
|
||||||
srcfile = os.path.join(os.path.dirname(__file__), "config", "kettle.json")
|
if self.config.check_for_setup() is False:
|
||||||
destfile = os.path.join(".", 'config')
|
return
|
||||||
shutil.copy(srcfile, destfile)
|
print("START")
|
||||||
|
cbpi = CraftBeerPi(self.config)
|
||||||
|
cbpi.start()
|
||||||
|
|
||||||
if os.path.exists(os.path.join(".", 'config', "fermenter_data.json")) is False:
|
def setup_one_wire(self):
|
||||||
srcfile = os.path.join(os.path.dirname(__file__), "config", "fermenter_data.json")
|
|
||||||
destfile = os.path.join(".", 'config')
|
|
||||||
shutil.copy(srcfile, destfile)
|
|
||||||
|
|
||||||
if os.path.exists(os.path.join(".", 'config', "step_data.json")) is False:
|
|
||||||
srcfile = os.path.join(os.path.dirname(__file__), "config", "step_data.json")
|
|
||||||
destfile = os.path.join(".", 'config')
|
|
||||||
shutil.copy(srcfile, destfile)
|
|
||||||
|
|
||||||
if os.path.exists(os.path.join(".", 'config', "config.json")) is False:
|
|
||||||
srcfile = os.path.join(os.path.dirname(__file__), "config", "config.json")
|
|
||||||
destfile = os.path.join(".", 'config')
|
|
||||||
shutil.copy(srcfile, destfile)
|
|
||||||
|
|
||||||
if os.path.exists(os.path.join(".", 'config', "dashboard", "cbpi_dashboard_1.json")) is False:
|
|
||||||
srcfile = os.path.join(os.path.dirname(__file__), "config", "dashboard", "cbpi_dashboard_1.json")
|
|
||||||
destfile = os.path.join(".", "config", "dashboard")
|
|
||||||
shutil.copy(srcfile, destfile)
|
|
||||||
|
|
||||||
if os.path.exists(os.path.join(".", 'config', "carftbeerpi.service")) is False:
|
|
||||||
srcfile = os.path.join(os.path.dirname(__file__), "config", "craftbeerpi.service")
|
|
||||||
destfile = os.path.join(".", 'config')
|
|
||||||
shutil.copy(srcfile, destfile)
|
|
||||||
|
|
||||||
if os.path.exists(os.path.join(".", 'config', "chromium.desktop")) is False:
|
|
||||||
srcfile = os.path.join(os.path.dirname(__file__), "config", "chromium.desktop")
|
|
||||||
destfile = os.path.join(".", 'config')
|
|
||||||
shutil.copy(srcfile, destfile)
|
|
||||||
|
|
||||||
print("Config Folder created")
|
|
||||||
|
|
||||||
|
|
||||||
def create_home_folder_structure():
|
|
||||||
pathlib.Path(os.path.join(".", 'logs/sensors')).mkdir(parents=True, exist_ok=True)
|
|
||||||
pathlib.Path(os.path.join(".", 'config')).mkdir(parents=True, exist_ok=True)
|
|
||||||
pathlib.Path(os.path.join(".", 'config/dashboard')).mkdir(parents=True, exist_ok=True)
|
|
||||||
pathlib.Path(os.path.join(".", 'config/dashboard/widgets')).mkdir(parents=True, exist_ok=True)
|
|
||||||
pathlib.Path(os.path.join(".", 'config/recipes')).mkdir(parents=True, exist_ok=True)
|
|
||||||
pathlib.Path(os.path.join(".", 'config/fermenterrecipes')).mkdir(parents=True, exist_ok=True)
|
|
||||||
pathlib.Path(os.path.join(".", 'config/upload')).mkdir(parents=True, exist_ok=True)
|
|
||||||
print("Folder created")
|
|
||||||
|
|
||||||
|
|
||||||
def setup_one_wire():
|
|
||||||
print("Setting up 1Wire")
|
print("Setting up 1Wire")
|
||||||
with open('/boot/config.txt', 'w') as f:
|
with open('/boot/config.txt', 'w') as f:
|
||||||
f.write("dtoverlay=w1-gpio,gpiopin=4,pullup=on")
|
f.write("dtoverlay=w1-gpio,gpiopin=4,pullup=on")
|
||||||
print("/boot/config.txt created")
|
print("/boot/config.txt created")
|
||||||
|
|
||||||
def list_one_wire():
|
def list_one_wire(self):
|
||||||
print("List 1Wire")
|
print("List 1Wire")
|
||||||
call(["modprobe", "w1-gpio"])
|
call(["modprobe", "w1-gpio"])
|
||||||
call(["modprobe", "w1-therm"])
|
call(["modprobe", "w1-therm"])
|
||||||
|
@ -108,88 +50,7 @@ def list_one_wire():
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
|
|
||||||
def copy_splash():
|
def plugins_list(self):
|
||||||
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 = []
|
result = []
|
||||||
print("")
|
print("")
|
||||||
print(Fore.LIGHTYELLOW_EX,"List of active plugins", Style.RESET_ALL)
|
print(Fore.LIGHTYELLOW_EX,"List of active plugins", Style.RESET_ALL)
|
||||||
|
@ -201,7 +62,6 @@ def plugins_list():
|
||||||
if name.startswith('cbpi') and len(name) > 4
|
if name.startswith('cbpi') and len(name) > 4
|
||||||
}
|
}
|
||||||
for key, module in discovered_plugins.items():
|
for key, module in discovered_plugins.items():
|
||||||
|
|
||||||
try:
|
try:
|
||||||
meta = metadata(key)
|
meta = metadata(key)
|
||||||
result.append(dict(Name=meta["Name"], Version=meta["Version"], Author=meta["Author"], Homepage=meta["Home-page"], Summary=meta["Summary"]))
|
result.append(dict(Name=meta["Name"], Version=meta["Version"], Author=meta["Author"], Homepage=meta["Home-page"], Summary=meta["Summary"]))
|
||||||
|
@ -211,9 +71,7 @@ def plugins_list():
|
||||||
print(Fore.LIGHTGREEN_EX, tabulate(result, headers="keys"), Style.RESET_ALL)
|
print(Fore.LIGHTGREEN_EX, tabulate(result, headers="keys"), Style.RESET_ALL)
|
||||||
|
|
||||||
|
|
||||||
|
def plugin_create(self):
|
||||||
def plugin_create():
|
|
||||||
|
|
||||||
print("Plugin Creation")
|
print("Plugin Creation")
|
||||||
print("")
|
print("")
|
||||||
|
|
||||||
|
@ -271,7 +129,6 @@ def plugin_create():
|
||||||
with open(os.path.join(".", name, name, "config.yaml"), "w") as fh:
|
with open(os.path.join(".", name, name, "config.yaml"), "w") as fh:
|
||||||
fh.write(outputText)
|
fh.write(outputText)
|
||||||
|
|
||||||
|
|
||||||
print("")
|
print("")
|
||||||
print("")
|
print("")
|
||||||
print("Plugin {}{}{} created! ".format(Fore.LIGHTGREEN_EX, name, Style.RESET_ALL) )
|
print("Plugin {}{}{} created! ".format(Fore.LIGHTGREEN_EX, name, Style.RESET_ALL) )
|
||||||
|
@ -282,66 +139,8 @@ def plugin_create():
|
||||||
print("")
|
print("")
|
||||||
print("")
|
print("")
|
||||||
|
|
||||||
|
def autostart(self, name):
|
||||||
|
'''Enable or disable autostart'''
|
||||||
@click.group()
|
|
||||||
def main():
|
|
||||||
print("---------------------")
|
|
||||||
print("Welcome to CBPi")
|
|
||||||
print("---------------------")
|
|
||||||
level = logging.INFO
|
|
||||||
logging.basicConfig(level=level, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
def setup():
|
|
||||||
'''Create Config folder'''
|
|
||||||
print("Setting up CraftBeerPi")
|
|
||||||
create_home_folder_structure()
|
|
||||||
create_config_file()
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@click.option('--list', is_flag=True, help="List all 1Wire Devices")
|
|
||||||
@click.option('--setup', is_flag=True, help="Setup 1Wire on Raspberry Pi")
|
|
||||||
def onewire(list, setup):
|
|
||||||
'''Setup 1wire on Raspberry Pi'''
|
|
||||||
if setup is True:
|
|
||||||
setup_one_wire()
|
|
||||||
if list is True:
|
|
||||||
list_one_wire()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
def start():
|
|
||||||
'''Lets go brewing'''
|
|
||||||
if check_for_setup() is False:
|
|
||||||
return
|
|
||||||
print("Starting up CraftBeerPi ...")
|
|
||||||
cbpi = CraftBeerPi()
|
|
||||||
cbpi.start()
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
def plugins():
|
|
||||||
'''List active plugins'''
|
|
||||||
plugins_list()
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
def create():
|
|
||||||
'''Create New Plugin'''
|
|
||||||
plugin_create()
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@click.argument('name')
|
|
||||||
def autostart(name):
|
|
||||||
'''(on|off|status) Enable or disable autostart'''
|
|
||||||
if(name == "status"):
|
if(name == "status"):
|
||||||
if os.path.exists(os.path.join("/etc/systemd/system","craftbeerpi.service")) is True:
|
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))
|
print("CraftBeerPi Autostart is {}ON{}".format(Fore.LIGHTGREEN_EX,Style.RESET_ALL))
|
||||||
|
@ -351,7 +150,7 @@ def autostart(name):
|
||||||
print("Add craftbeerpi.service to systemd")
|
print("Add craftbeerpi.service to systemd")
|
||||||
try:
|
try:
|
||||||
if os.path.exists(os.path.join("/etc/systemd/system","craftbeerpi.service")) is False:
|
if os.path.exists(os.path.join("/etc/systemd/system","craftbeerpi.service")) is False:
|
||||||
srcfile = os.path.join(".", "config", "craftbeerpi.service")
|
srcfile = self.config.get_file_path("craftbeerpi.service")
|
||||||
destfile = os.path.join("/etc/systemd/system")
|
destfile = os.path.join("/etc/systemd/system")
|
||||||
shutil.copy(srcfile, destfile)
|
shutil.copy(srcfile, destfile)
|
||||||
print("Copied craftbeerpi.service to /etc/systemd/system")
|
print("Copied craftbeerpi.service to /etc/systemd/system")
|
||||||
|
@ -388,12 +187,8 @@ def autostart(name):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def chromium(self, name):
|
||||||
|
'''Enable or disable autostart'''
|
||||||
@click.command()
|
|
||||||
@click.argument('name')
|
|
||||||
def chromium(name):
|
|
||||||
'''(on|off|status) Enable or disable Kiosk mode'''
|
|
||||||
if(name == "status"):
|
if(name == "status"):
|
||||||
if os.path.exists(os.path.join("/etc/xdg/autostart/","chromium.desktop")) is True:
|
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))
|
print("CraftBeerPi Chromium Desktop is {}ON{}".format(Fore.LIGHTGREEN_EX,Style.RESET_ALL))
|
||||||
|
@ -403,7 +198,7 @@ def chromium(name):
|
||||||
print("Add chromium.desktop to /etc/xdg/autostart/")
|
print("Add chromium.desktop to /etc/xdg/autostart/")
|
||||||
try:
|
try:
|
||||||
if os.path.exists(os.path.join("/etc/xdg/autostart/","chromium.desktop")) is False:
|
if os.path.exists(os.path.join("/etc/xdg/autostart/","chromium.desktop")) is False:
|
||||||
srcfile = os.path.join(".", "config", "chromium.desktop")
|
srcfile = self.config.get_file_path("chromium.desktop")
|
||||||
destfile = os.path.join("/etc/xdg/autostart/")
|
destfile = os.path.join("/etc/xdg/autostart/")
|
||||||
shutil.copy(srcfile, destfile)
|
shutil.copy(srcfile, destfile)
|
||||||
print("Copied chromium.desktop to /etc/xdg/autostart/")
|
print("Copied chromium.desktop to /etc/xdg/autostart/")
|
||||||
|
@ -426,10 +221,65 @@ def chromium(name):
|
||||||
return
|
return
|
||||||
return
|
return
|
||||||
|
|
||||||
main.add_command(setup)
|
|
||||||
main.add_command(start)
|
@click.group()
|
||||||
main.add_command(autostart)
|
@click.pass_context
|
||||||
main.add_command(chromium)
|
@click.option('--config-folder-path', '-c', default="./config", type=click.Path(), help="Specify where the config folder is located. Defaults to './config'.")
|
||||||
main.add_command(plugins)
|
def main(context, config_folder_path):
|
||||||
main.add_command(onewire)
|
print("---------------------")
|
||||||
main.add_command(create)
|
print("Welcome to CBPi")
|
||||||
|
print("---------------------")
|
||||||
|
level = logging.INFO
|
||||||
|
logging.basicConfig(level=level, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
|
||||||
|
cbpi_cli = CraftBeerPiCli(ConfigFolder(config_folder_path))
|
||||||
|
context.obj = cbpi_cli
|
||||||
|
pass
|
||||||
|
|
||||||
|
@main.command()
|
||||||
|
@click.pass_context
|
||||||
|
def setup(context):
|
||||||
|
'''Create Config folder'''
|
||||||
|
context.obj.setup()
|
||||||
|
|
||||||
|
@main.command()
|
||||||
|
@click.pass_context
|
||||||
|
@click.option('--list', is_flag=True, help="List all 1Wire Devices")
|
||||||
|
@click.option('--setup', is_flag=True, help="Setup 1Wire on Raspberry Pi")
|
||||||
|
def onewire(context, list, setup):
|
||||||
|
'''Setup 1wire on Raspberry Pi'''
|
||||||
|
if setup is True:
|
||||||
|
context.obj.setup_one_wire()
|
||||||
|
if list is True:
|
||||||
|
context.obj.list_one_wire()
|
||||||
|
|
||||||
|
@main.command()
|
||||||
|
@click.pass_context
|
||||||
|
def start(context):
|
||||||
|
context.obj.start()
|
||||||
|
|
||||||
|
@main.command()
|
||||||
|
@click.pass_context
|
||||||
|
def plugins(context):
|
||||||
|
'''List active plugins'''
|
||||||
|
context.obj.plugins_list()
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.pass_context
|
||||||
|
def create(context):
|
||||||
|
'''Create New Plugin'''
|
||||||
|
context.obj.plugin_create()
|
||||||
|
|
||||||
|
@main.command()
|
||||||
|
@click.pass_context
|
||||||
|
@click.argument('name')
|
||||||
|
def autostart(context, name):
|
||||||
|
'''(on|off|status) Enable or disable autostart'''
|
||||||
|
context.obj.autostart(name)
|
||||||
|
|
||||||
|
|
||||||
|
@main.command()
|
||||||
|
@click.pass_context
|
||||||
|
@click.argument('name')
|
||||||
|
def chromium(context, name):
|
||||||
|
'''(on|off|status) Enable or disable Kiosk mode'''
|
||||||
|
context.obj.chromium(name)
|
||||||
|
|
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.data = []
|
||||||
self.autostart = True
|
self.autostart = True
|
||||||
#self._loop = asyncio.get_event_loop()
|
#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)
|
self.cbpi.app.on_cleanup.append(self.shutdown)
|
||||||
|
|
||||||
async def init(self):
|
async def init(self):
|
||||||
|
@ -36,6 +36,7 @@ class BasicController:
|
||||||
logging.info("{} Load ".format(self.name))
|
logging.info("{} Load ".format(self.name))
|
||||||
with open(self.path) as json_file:
|
with open(self.path) as json_file:
|
||||||
data = json.load(json_file)
|
data = json.load(json_file)
|
||||||
|
data['data'].sort(key=lambda x: x.get('name').upper())
|
||||||
|
|
||||||
for i in data["data"]:
|
for i in data["data"]:
|
||||||
self.data.append(self.create(i))
|
self.data.append(self.create(i))
|
||||||
|
@ -151,4 +152,4 @@ class BasicController:
|
||||||
item = self.find_by_id(id)
|
item = self.find_by_id(id)
|
||||||
await item.instance.__getattribute__(action)(**parameter)
|
await item.instance.__getattribute__(action)(**parameter)
|
||||||
except Exception as e:
|
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.logger = logging.getLogger(__name__)
|
||||||
self.cbpi = cbpi
|
self.cbpi = cbpi
|
||||||
self.cbpi.register(self)
|
self.cbpi.register(self)
|
||||||
self.path = os.path.join(".", 'config', "config.json")
|
self.path = cbpi.config_folder.get_file_path("config.json")
|
||||||
self.path_static = os.path.join(".", 'config', "config.yaml")
|
self.path_static = cbpi.config_folder.get_file_path("config.yaml")
|
||||||
|
|
||||||
def get_state(self):
|
def get_state(self):
|
||||||
|
|
||||||
|
|
|
@ -18,14 +18,14 @@ class DashboardController:
|
||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger(__name__)
|
||||||
self.cbpi.register(self)
|
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):
|
async def init(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def get_content(self, dashboard_id):
|
async def get_content(self, dashboard_id):
|
||||||
try:
|
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)
|
logging.info(self.path)
|
||||||
with open(self.path) as json_file:
|
with open(self.path) as json_file:
|
||||||
data = json.load(json_file)
|
data = json.load(json_file)
|
||||||
|
@ -35,21 +35,21 @@ class DashboardController:
|
||||||
|
|
||||||
async def add_content(self, dashboard_id, data):
|
async def add_content(self, dashboard_id, data):
|
||||||
print(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:
|
with open(self.path, 'w') as outfile:
|
||||||
json.dump(data, outfile, indent=4, sort_keys=True)
|
json.dump(data, outfile, indent=4, sort_keys=True)
|
||||||
self.cbpi.notify(title="Dashboard {}".format(dashboard_id), message="Saved Successfully", type=NotificationType.SUCCESS)
|
self.cbpi.notify(title="Dashboard {}".format(dashboard_id), message="Saved Successfully", type=NotificationType.SUCCESS)
|
||||||
return {"status": "OK"}
|
return {"status": "OK"}
|
||||||
|
|
||||||
async def delete_content(self, dashboard_id):
|
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):
|
if os.path.exists(self.path):
|
||||||
os.remove(self.path)
|
os.remove(self.path)
|
||||||
self.cbpi.notify(title="Dashboard {}".format(dashboard_id), message="Deleted Successfully", type=NotificationType.SUCCESS)
|
self.cbpi.notify(title="Dashboard {}".format(dashboard_id), message="Deleted Successfully", type=NotificationType.SUCCESS)
|
||||||
|
|
||||||
|
|
||||||
async def get_custom_widgets(self):
|
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")]
|
onlyfiles = [os.path.splitext(f)[0] for f in sorted(listdir(path)) if isfile(join(path, f)) and f.endswith(".svg")]
|
||||||
return onlyfiles
|
return onlyfiles
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ class FermentationController:
|
||||||
self.update_key = "fermenterupdate"
|
self.update_key = "fermenterupdate"
|
||||||
self.cbpi = cbpi
|
self.cbpi = cbpi
|
||||||
self.logger = logging.getLogger(__name__)
|
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.data = []
|
||||||
self.types = {}
|
self.types = {}
|
||||||
self.steptypes = {}
|
self.steptypes = {}
|
||||||
|
@ -35,16 +35,16 @@ class FermentationController:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def check_fermenter_file(self):
|
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")
|
logging.info("INIT fermenter_data.json file")
|
||||||
data = {
|
data = {
|
||||||
"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)
|
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()
|
self.save()
|
||||||
|
@ -114,14 +114,17 @@ class FermentationController:
|
||||||
id = data.get("id")
|
id = data.get("id")
|
||||||
name = data.get("name")
|
name = data.get("name")
|
||||||
sensor = data.get("sensor")
|
sensor = data.get("sensor")
|
||||||
|
pressure_sensor = data.get("pressure_sensor")
|
||||||
heater = data.get("heater")
|
heater = data.get("heater")
|
||||||
cooler = data.get("cooler")
|
cooler = data.get("cooler")
|
||||||
|
valve = data.get("valve","")
|
||||||
logictype = data.get("type")
|
logictype = data.get("type")
|
||||||
temp = data.get("target_temp")
|
temp = data.get("target_temp")
|
||||||
|
pressure = data.get("target_pressure")
|
||||||
brewname = data.get("brewname")
|
brewname = data.get("brewname")
|
||||||
description = data.get("description")
|
description = data.get("description")
|
||||||
props = Props(data.get("props", {}))
|
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", [])))
|
fermenter.steps = list(map(lambda item: self._create_step(fermenter, item), data.get("steps", [])))
|
||||||
self.push_update()
|
self.push_update()
|
||||||
return fermenter
|
return fermenter
|
||||||
|
@ -197,13 +200,16 @@ class FermentationController:
|
||||||
def _update(old_item: Fermenter, item: Fermenter):
|
def _update(old_item: Fermenter, item: Fermenter):
|
||||||
old_item.name = item.name
|
old_item.name = item.name
|
||||||
old_item.sensor = item.sensor
|
old_item.sensor = item.sensor
|
||||||
|
old_item.pressure_sensor = item.pressure_sensor
|
||||||
old_item.heater = item.heater
|
old_item.heater = item.heater
|
||||||
old_item.cooler = item.cooler
|
old_item.cooler = item.cooler
|
||||||
|
old_item.valve = item.valve
|
||||||
old_item.type = item.type
|
old_item.type = item.type
|
||||||
old_item.brewname = item.brewname
|
old_item.brewname = item.brewname
|
||||||
old_item.description = item.description
|
old_item.description = item.description
|
||||||
old_item.props = item.props
|
old_item.props = item.props
|
||||||
old_item.target_temp = item.target_temp
|
old_item.target_temp = item.target_temp
|
||||||
|
old_item.target_pressure = item.target_pressure
|
||||||
return old_item
|
return old_item
|
||||||
|
|
||||||
self.data = list(map(lambda old: _update(old, item) if old.id == item.id else old, self.data))
|
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:
|
except Exception as e:
|
||||||
logging.error("Failed to set Target Temp {} {}".format(id, 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 ):
|
async def delete(self, id: str ):
|
||||||
item = self._find_by_id(id)
|
item = self._find_by_id(id)
|
||||||
self.data = list(filter(lambda item: item.id != id, self.data))
|
self.data = list(filter(lambda item: item.id != id, self.data))
|
||||||
|
@ -525,7 +542,7 @@ class FermentationController:
|
||||||
|
|
||||||
async def savetobook(self, fermenterid):
|
async def savetobook(self, fermenterid):
|
||||||
name = shortuuid.uuid()
|
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)
|
fermenter=self._find_by_id(fermenterid)
|
||||||
try:
|
try:
|
||||||
brewname = fermenter.brewname
|
brewname = fermenter.brewname
|
||||||
|
|
|
@ -29,56 +29,52 @@ class FermenterRecipeController:
|
||||||
|
|
||||||
async def create(self, name):
|
async def create(self, name):
|
||||||
id = shortuuid.uuid()
|
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=[])
|
data = dict(basic=dict(name=name), steps=[])
|
||||||
with open(path, "w") as file:
|
with open(path, "w") as file:
|
||||||
yaml.dump(data, file)
|
yaml.dump(data, file)
|
||||||
return id
|
return id
|
||||||
|
|
||||||
async def save(self, name, data):
|
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)
|
logging.info(data)
|
||||||
with open(path, "w") as file:
|
with open(path, "w") as file:
|
||||||
yaml.dump(data, file, indent=4, sort_keys=True)
|
yaml.dump(data, file, indent=4, sort_keys=True)
|
||||||
|
|
||||||
|
|
||||||
async def get_recipes(self):
|
async def get_recipes(self):
|
||||||
path = os.path.join(".", 'config', "fermenterrecipes")
|
fermenter_recipe_ids = self.cbpi.config_folder.get_all_fermenter_recipes()
|
||||||
onlyfiles = [os.path.splitext(f)[0] for f in listdir(path) if isfile(join(path, f)) and f.endswith(".yaml")]
|
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
for filename in onlyfiles:
|
for recipe_id in fermenter_recipe_ids:
|
||||||
recipe_path = os.path.join(".", 'config', "fermenterrecipes", "%s.yaml" % filename)
|
|
||||||
with open(recipe_path) as file:
|
with open(self.cbpi.config_folder.get_fermenter_recipe_by_id(recipe_id)) as file:
|
||||||
data = yaml.load(file, Loader=yaml.FullLoader)
|
data = yaml.load(file, Loader=yaml.FullLoader)
|
||||||
dataset = data["basic"]
|
dataset = data["basic"]
|
||||||
dataset["file"] = filename
|
dataset["file"] = recipe_id
|
||||||
result.append(dataset)
|
result.append(dataset)
|
||||||
logging.info(result)
|
logging.info(result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def get_by_name(self, name):
|
async def get_by_name(self, name):
|
||||||
|
recipe_path = self.cbpi.config_folder.get_fermenter_recipe_by_id(name)
|
||||||
recipe_path = os.path.join(".", 'config', "fermenterrecipes", "%s.yaml" % name)
|
|
||||||
with open(recipe_path) as file:
|
with open(recipe_path) as file:
|
||||||
return yaml.load(file, Loader=yaml.FullLoader)
|
return yaml.load(file, Loader=yaml.FullLoader)
|
||||||
|
|
||||||
|
|
||||||
async def remove(self, name):
|
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)
|
os.remove(path)
|
||||||
|
|
||||||
|
|
||||||
async def brew(self, recipeid, fermenterid, name):
|
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)
|
logging.info(recipe_path)
|
||||||
with open(recipe_path) as file:
|
with open(recipe_path) as file:
|
||||||
data = yaml.load(file, Loader=yaml.FullLoader)
|
data = yaml.load(file, Loader=yaml.FullLoader)
|
||||||
await self.cbpi.fermenter.load_recipe(data, fermenterid, name)
|
await self.cbpi.fermenter.load_recipe(data, fermenterid, name)
|
||||||
|
|
||||||
async def clone(self, id, new_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:
|
with open(recipe_path) as file:
|
||||||
data = yaml.load(file, Loader=yaml.FullLoader)
|
data = yaml.load(file, Loader=yaml.FullLoader)
|
||||||
data["basic"]["name"] = new_name
|
data["basic"]["name"] = new_name
|
||||||
|
|
|
@ -22,7 +22,7 @@ class NotificationController:
|
||||||
try:
|
try:
|
||||||
del self.listener[listener_id]
|
del self.listener[listener_id]
|
||||||
except:
|
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):
|
async def _call_listener(self, title, message, type, action):
|
||||||
for id, method in self.listener.items():
|
for id, method in self.listener.items():
|
||||||
|
@ -60,5 +60,5 @@ class NotificationController:
|
||||||
asyncio.create_task(action.method())
|
asyncio.create_task(action.method())
|
||||||
del self.callback_cache[notification_id]
|
del self.callback_cache[notification_id]
|
||||||
except Exception as e:
|
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):
|
async def create(self, name):
|
||||||
id = shortuuid.uuid()
|
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=[])
|
data = dict(basic=dict(name=name, author=self.cbpi.config.get("AUTHOR", "John Doe")), steps=[])
|
||||||
with open(path, "w") as file:
|
with open(path, "w") as file:
|
||||||
yaml.dump(data, file)
|
yaml.dump(data, file)
|
||||||
return id
|
return id
|
||||||
|
|
||||||
async def save(self, name, data):
|
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)
|
logging.info(data)
|
||||||
with open(path, "w") as file:
|
with open(path, "w") as file:
|
||||||
yaml.dump(data, file, indent=4, sort_keys=True)
|
yaml.dump(data, file, indent=4, sort_keys=True)
|
||||||
|
|
||||||
|
|
||||||
async def get_recipes(self):
|
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")]
|
onlyfiles = [os.path.splitext(f)[0] for f in listdir(path) if isfile(join(path, f)) and f.endswith(".yaml")]
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
for filename in onlyfiles:
|
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:
|
with open(recipe_path) as file:
|
||||||
data = yaml.load(file, Loader=yaml.FullLoader)
|
data = yaml.load(file, Loader=yaml.FullLoader)
|
||||||
dataset = data["basic"]
|
dataset = data["basic"]
|
||||||
|
@ -58,25 +58,25 @@ class RecipeController:
|
||||||
|
|
||||||
async def get_by_name(self, name):
|
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:
|
with open(recipe_path) as file:
|
||||||
return yaml.load(file, Loader=yaml.FullLoader)
|
return yaml.load(file, Loader=yaml.FullLoader)
|
||||||
|
|
||||||
|
|
||||||
async def remove(self, name):
|
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)
|
os.remove(path)
|
||||||
|
|
||||||
|
|
||||||
async def brew(self, name):
|
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:
|
with open(recipe_path) as file:
|
||||||
data = yaml.load(file, Loader=yaml.FullLoader)
|
data = yaml.load(file, Loader=yaml.FullLoader)
|
||||||
await self.cbpi.step.load_recipe(data)
|
await self.cbpi.step.load_recipe(data)
|
||||||
|
|
||||||
async def clone(self, id, new_name):
|
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:
|
with open(recipe_path) as file:
|
||||||
data = yaml.load(file, Loader=yaml.FullLoader)
|
data = yaml.load(file, Loader=yaml.FullLoader)
|
||||||
data["basic"]["name"] = new_name
|
data["basic"]["name"] = new_name
|
||||||
|
|
|
@ -12,7 +12,7 @@ class SensorController(BasicController):
|
||||||
instance = data.get("instance")
|
instance = data.get("instance")
|
||||||
state =instance.get_state()
|
state =instance.get_state()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error("Faild to create sensor dict {} ".format(e))
|
logging.error("Failed to create sensor dict {} ".format(e))
|
||||||
state = dict()
|
state = dict()
|
||||||
|
|
||||||
return dict(name=data.get("name"), id=data.get("id"), type=data.get("type"), state=state,props=data.get("props", []))
|
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:
|
try:
|
||||||
return self.find_by_id(id).instance.get_state()
|
return self.find_by_id(id).instance.get_state()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error("Faild read sensor value {} {} ".format(id, e))
|
logging.error("Failed read sensor value {} {} ".format(id, e))
|
||||||
return None
|
return None
|
|
@ -19,7 +19,7 @@ class StepController:
|
||||||
def __init__(self, cbpi):
|
def __init__(self, cbpi):
|
||||||
self.cbpi = cbpi
|
self.cbpi = cbpi
|
||||||
self.logger = logging.getLogger(__name__)
|
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._loop = asyncio.get_event_loop()
|
||||||
self.basic_data = {}
|
self.basic_data = {}
|
||||||
self.step = None
|
self.step = None
|
||||||
|
@ -230,7 +230,7 @@ class StepController:
|
||||||
await self.save()
|
await self.save()
|
||||||
|
|
||||||
async def shutdown(self, app=None):
|
async def shutdown(self, app=None):
|
||||||
logging.info("Mash Profile Shutdonw")
|
logging.info("Mash Profile Shutdown")
|
||||||
for p in self.profile:
|
for p in self.profile:
|
||||||
instance = p.instance
|
instance = p.instance
|
||||||
# Stopping all running task
|
# Stopping all running task
|
||||||
|
@ -279,7 +279,7 @@ class StepController:
|
||||||
await step.instance.start()
|
await step.instance.start()
|
||||||
step.status = StepState.ACTIVE
|
step.status = StepState.ACTIVE
|
||||||
except Exception as e:
|
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):
|
async def save_basic(self, data):
|
||||||
logging.info("SAVE Basic Data")
|
logging.info("SAVE Basic Data")
|
||||||
|
@ -293,7 +293,7 @@ class StepController:
|
||||||
item = self.find_by_id(id)
|
item = self.find_by_id(id)
|
||||||
await item.instance.__getattribute__(action)(**parameter)
|
await item.instance.__getattribute__(action)(**parameter)
|
||||||
except Exception as e:
|
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):
|
async def load_recipe(self, data):
|
||||||
try:
|
try:
|
||||||
|
@ -324,7 +324,7 @@ class StepController:
|
||||||
|
|
||||||
async def savetobook(self):
|
async def savetobook(self):
|
||||||
name = shortuuid.uuid()
|
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)))
|
data = dict(basic=self.basic_data, steps=list(map(lambda item: item.to_dict(), self.profile)))
|
||||||
with open(path, "w") as file:
|
with open(path, "w") as file:
|
||||||
yaml.dump(data, file)
|
yaml.dump(data, file)
|
||||||
|
|
|
@ -38,7 +38,7 @@ class SystemController:
|
||||||
|
|
||||||
async def backupConfig(self):
|
async def backupConfig(self):
|
||||||
output_filename = "cbpi4_config"
|
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)
|
shutil.make_archive(output_filename, 'zip', dir_name)
|
||||||
|
|
||||||
async def downloadlog(self, logtime):
|
async def downloadlog(self, logtime):
|
||||||
|
@ -153,7 +153,7 @@ class SystemController:
|
||||||
try:
|
try:
|
||||||
content = svg_file.read().decode('utf-8','replace')
|
content = svg_file.read().decode('utf-8','replace')
|
||||||
if svg_file and self.allowed_file(filename, 'svg'):
|
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)
|
logging.info(self.path)
|
||||||
|
|
||||||
f=open(self.path, "w")
|
f=open(self.path, "w")
|
||||||
|
|
|
@ -32,7 +32,7 @@ class UploadController:
|
||||||
|
|
||||||
async def get_kbh_recipes(self):
|
async def get_kbh_recipes(self):
|
||||||
try:
|
try:
|
||||||
path = os.path.join(".", 'config', "upload", "kbh.db")
|
path = self.cbpi.config_folder.get_upload_file("kbh.db")
|
||||||
conn = sqlite3.connect(path)
|
conn = sqlite3.connect(path)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
c.execute('SELECT ID, Sudname, Status FROM Sud')
|
c.execute('SELECT ID, Sudname, Status FROM Sud')
|
||||||
|
@ -47,7 +47,7 @@ class UploadController:
|
||||||
|
|
||||||
async def get_xml_recipes(self):
|
async def get_xml_recipes(self):
|
||||||
try:
|
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()
|
e = xml.etree.ElementTree.parse(path).getroot()
|
||||||
result =[]
|
result =[]
|
||||||
counter = 1
|
counter = 1
|
||||||
|
@ -61,7 +61,7 @@ class UploadController:
|
||||||
|
|
||||||
async def get_json_recipes(self):
|
async def get_json_recipes(self):
|
||||||
try:
|
try:
|
||||||
path = os.path.join(".", 'config', "upload", "mmum.json")
|
path = self.cbpi.config_folder.get_upload_file("mmum.json")
|
||||||
e = json.load(open(path))
|
e = json.load(open(path))
|
||||||
result =[]
|
result =[]
|
||||||
result.append({'value': str(1), 'label': e['Name']})
|
result.append({'value': str(1), 'label': e['Name']})
|
||||||
|
@ -123,7 +123,7 @@ class UploadController:
|
||||||
try:
|
try:
|
||||||
beer_xml = recipe_file.read().decode('utf-8','replace')
|
beer_xml = recipe_file.read().decode('utf-8','replace')
|
||||||
if recipe_file and self.allowed_file(filename, 'xml'):
|
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 = open(self.path, "w")
|
||||||
f.write(beer_xml)
|
f.write(beer_xml)
|
||||||
|
@ -137,7 +137,7 @@ class UploadController:
|
||||||
try:
|
try:
|
||||||
mmum_json = recipe_file.read().decode('utf-8','replace')
|
mmum_json = recipe_file.read().decode('utf-8','replace')
|
||||||
if recipe_file and self.allowed_file(filename, 'json'):
|
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 = open(self.path, "w")
|
||||||
f.write(mmum_json)
|
f.write(mmum_json)
|
||||||
|
@ -151,7 +151,7 @@ class UploadController:
|
||||||
try:
|
try:
|
||||||
content = recipe_file.read()
|
content = recipe_file.read()
|
||||||
if recipe_file and self.allowed_file(filename, 'sqlite'):
|
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=open(self.path, "wb")
|
||||||
f.write(content)
|
f.write(content)
|
||||||
|
@ -168,7 +168,7 @@ class UploadController:
|
||||||
config = self.get_config_values()
|
config = self.get_config_values()
|
||||||
if self.kettle is not None:
|
if self.kettle is not None:
|
||||||
# load beerxml file located in upload folder
|
# 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:
|
if os.path.exists(self.path) is False:
|
||||||
self.cbpi.notify("File Not Found", "Please upload a kbh V2 databsel file", NotificationType.ERROR)
|
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)
|
self.cbpi.notify('Recipe Upload', 'No default Kettle defined. Please specify default Kettle in settings', NotificationType.ERROR)
|
||||||
|
|
||||||
def findMax(self, string):
|
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))
|
e = json.load(open(self.path))
|
||||||
for idx in range(1,20):
|
for idx in range(1,20):
|
||||||
search_string = string.replace("%%",str(idx))
|
search_string = string.replace("%%",str(idx))
|
||||||
|
@ -328,7 +328,7 @@ class UploadController:
|
||||||
return i
|
return i
|
||||||
|
|
||||||
def getJsonMashin(self, id):
|
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))
|
e = json.load(open(self.path))
|
||||||
return float(e['Infusion_Einmaischtemperatur'])
|
return float(e['Infusion_Einmaischtemperatur'])
|
||||||
|
|
||||||
|
@ -337,7 +337,7 @@ class UploadController:
|
||||||
|
|
||||||
if self.kettle is not None:
|
if self.kettle is not None:
|
||||||
# load mmum-json file located in upload folder
|
# 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:
|
if os.path.exists(self.path) is False:
|
||||||
self.cbpi.notify("File Not Found", "Please upload a MMuM-JSON File", NotificationType.ERROR)
|
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:
|
if self.kettle is not None:
|
||||||
# load beerxml file located in upload folder
|
# 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:
|
if os.path.exists(self.path) is False:
|
||||||
self.cbpi.notify("File Not Found", "Please upload a Beer.xml File", NotificationType.ERROR)
|
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)
|
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)})
|
steps.append({"name": e.find("NAME").text, "temp": temp, "timer": float(e.find("STEP_TIME").text)})
|
||||||
elif recipe_type == "json":
|
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))
|
e = json.load(open(self.path))
|
||||||
for idx in range(1,self.findMax("Infusion_Rastzeit%%")):
|
for idx in range(1,self.findMax("Infusion_Rastzeit%%")):
|
||||||
if self.cbpi.config.get("TEMP_UNIT", "C") == "C":
|
if self.cbpi.config.get("TEMP_UNIT", "C") == "C":
|
||||||
|
|
|
@ -86,7 +86,7 @@ async def error_middleware(request, handler):
|
||||||
|
|
||||||
class CraftBeerPi:
|
class CraftBeerPi:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, configFolder):
|
||||||
|
|
||||||
operationsystem= sys.platform
|
operationsystem= sys.platform
|
||||||
if operationsystem.startswith('win'):
|
if operationsystem.startswith('win'):
|
||||||
|
@ -97,7 +97,8 @@ class CraftBeerPi:
|
||||||
self.version = __version__
|
self.version = __version__
|
||||||
self.codename = __codename__
|
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")
|
logger.info("Init CraftBeerPI")
|
||||||
|
|
||||||
|
@ -288,6 +289,7 @@ class CraftBeerPi:
|
||||||
self._setup_http_index()
|
self._setup_http_index()
|
||||||
self.plugin.load_plugins()
|
self.plugin.load_plugins()
|
||||||
self.plugin.load_plugins_from_evn()
|
self.plugin.load_plugins_from_evn()
|
||||||
|
await self.fermenter.init()
|
||||||
await self.sensor.init()
|
await self.sensor.init()
|
||||||
await self.step.init()
|
await self.step.init()
|
||||||
|
|
||||||
|
@ -295,7 +297,7 @@ class CraftBeerPi:
|
||||||
await self.kettle.init()
|
await self.kettle.init()
|
||||||
await self.call_initializer(self.app)
|
await self.call_initializer(self.app)
|
||||||
await self.dashboard.init()
|
await self.dashboard.init()
|
||||||
await self.fermenter.init()
|
|
||||||
|
|
||||||
self._swagger_setup()
|
self._swagger_setup()
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ class ConfigUpdate(CBPiExtension):
|
||||||
influxdbpwd = self.cbpi.config.get("INFLUXDBPWD", None)
|
influxdbpwd = self.cbpi.config.get("INFLUXDBPWD", None)
|
||||||
influxdbcloud = self.cbpi.config.get("INFLUXDBCLOUD", None)
|
influxdbcloud = self.cbpi.config.get("INFLUXDBCLOUD", None)
|
||||||
mqttupdate = self.cbpi.config.get("MQTTUpdate", None)
|
mqttupdate = self.cbpi.config.get("MQTTUpdate", None)
|
||||||
|
PRESSURE_UNIT = self.cbpi.config.get("PRESSURE_UNIT", None)
|
||||||
|
|
||||||
|
|
||||||
if boil_temp is None:
|
if boil_temp is None:
|
||||||
|
@ -276,6 +276,15 @@ class ConfigUpdate(CBPiExtension):
|
||||||
except:
|
except:
|
||||||
logger.warning('Unable to update database')
|
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):
|
def setup(cbpi):
|
||||||
cbpi.plugin.register("ConfigUpdate", ConfigUpdate)
|
cbpi.plugin.register("ConfigUpdate", ConfigUpdate)
|
||||||
|
|
|
@ -94,6 +94,7 @@ class FermenterTargetTempStep(CBPiFermentationStep):
|
||||||
self.AutoMode = True if self.props.get("AutoMode","No") == "Yes" else False
|
self.AutoMode = True if self.props.get("AutoMode","No") == "Yes" else False
|
||||||
if self.fermenter is not None:
|
if self.fermenter is not None:
|
||||||
self.fermenter.target_temp = float(self.props.get("Temp", 0))
|
self.fermenter.target_temp = float(self.props.get("Temp", 0))
|
||||||
|
self.fermenter.target_pressure = 0
|
||||||
if self.AutoMode == True:
|
if self.AutoMode == True:
|
||||||
await self.setAutoMode(True)
|
await self.setAutoMode(True)
|
||||||
self.summary = "Waiting for Target Temp"
|
self.summary = "Waiting for Target Temp"
|
||||||
|
@ -150,8 +151,9 @@ class FermenterTargetTempStep(CBPiFermentationStep):
|
||||||
@parameters([Property.Number(label="TimerD", description="Timer Days", configurable=True),
|
@parameters([Property.Number(label="TimerD", description="Timer Days", configurable=True),
|
||||||
Property.Number(label="TimerH", description="Timer Hours", configurable=True),
|
Property.Number(label="TimerH", description="Timer Hours", configurable=True),
|
||||||
Property.Number(label="TimerM", description="Timer Minutes", configurable=True),
|
Property.Number(label="TimerM", description="Timer Minutes", configurable=True),
|
||||||
Property.Number(label="Temp", configurable=True),
|
Property.Number(label="Temp", configurable=True, description="Step Temperature"),
|
||||||
Property.Sensor(label="Sensor"),
|
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")])
|
Property.Select(label="AutoMode",options=["Yes","No"], description="Switch Fermenterlogic automatically on and off -> Yes")])
|
||||||
class FermenterStep(CBPiFermentationStep):
|
class FermenterStep(CBPiFermentationStep):
|
||||||
|
|
||||||
|
@ -206,6 +208,7 @@ class FermenterStep(CBPiFermentationStep):
|
||||||
self.AutoMode = True if self.props.get("AutoMode", "No") == "Yes" else False
|
self.AutoMode = True if self.props.get("AutoMode", "No") == "Yes" else False
|
||||||
if self.fermenter is not None:
|
if self.fermenter is not None:
|
||||||
self.fermenter.target_temp = float(self.props.get("Temp", 0))
|
self.fermenter.target_temp = float(self.props.get("Temp", 0))
|
||||||
|
self.fermenter.target_pressure = float(self.props.get("Pressure", 0))
|
||||||
if self.AutoMode == True:
|
if self.AutoMode == True:
|
||||||
await self.setAutoMode(True)
|
await self.setAutoMode(True)
|
||||||
await self.push_update()
|
await self.push_update()
|
||||||
|
@ -293,8 +296,9 @@ class FermenterStep(CBPiFermentationStep):
|
||||||
logging.error("Failed to switch on FermenterLogic {} {}".format(self.fermenter.id, e))
|
logging.error("Failed to switch on FermenterLogic {} {}".format(self.fermenter.id, e))
|
||||||
|
|
||||||
@parameters([Property.Number(label="Temp", configurable=True, description = "Ramp to this temp"),
|
@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.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.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")])
|
Property.Select(label="AutoMode",options=["Yes","No"], description="Switch Fermenterlogic automatically on and off -> Yes")])
|
||||||
class FermenterRampTempStep(CBPiFermentationStep):
|
class FermenterRampTempStep(CBPiFermentationStep):
|
||||||
|
@ -324,6 +328,7 @@ class FermenterRampTempStep(CBPiFermentationStep):
|
||||||
logging.info(self.rate)
|
logging.info(self.rate)
|
||||||
self.target_temp = round(float(self.props.get("Temp", 0))*10)/10
|
self.target_temp = round(float(self.props.get("Temp", 0))*10)/10
|
||||||
logging.info(self.target_temp)
|
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:
|
while self.get_sensor_value(self.props.get("Sensor", None)).get("value") > 900:
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
self.starttemp = self.get_sensor_value(self.props.get("Sensor", None)).get("value")
|
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:
|
except asyncio.CancelledError as e:
|
||||||
pass
|
pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error("CustomLogic Error {}".format(e))
|
logging.error("Fermenter Hysteresis Error {}".format(e))
|
||||||
finally:
|
finally:
|
||||||
self.running = False
|
self.running = False
|
||||||
if self.heater:
|
if self.heater:
|
||||||
|
@ -109,6 +109,111 @@ class FermenterHysteresis(CBPiFermenterLogic):
|
||||||
await self.actor_off(self.cooler)
|
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):
|
def setup(cbpi):
|
||||||
|
|
||||||
|
@ -119,7 +224,7 @@ def setup(cbpi):
|
||||||
:param cbpi: the cbpi core
|
:param cbpi: the cbpi core
|
||||||
:return:
|
:return:
|
||||||
'''
|
'''
|
||||||
|
cbpi.plugin.register("Fermenter Spunding Hysteresis", FermenterSpundingHysteresis)
|
||||||
cbpi.plugin.register("Fermenter Hysteresis", FermenterHysteresis)
|
cbpi.plugin.register("Fermenter Hysteresis", FermenterHysteresis)
|
||||||
cbpi.plugin.register("Fermenter Autostart", FermenterAutostart)
|
cbpi.plugin.register("Fermenter Autostart", FermenterAutostart)
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import asyncio
|
import asyncio
|
||||||
import random
|
import random
|
||||||
|
import logging
|
||||||
from cbpi.api import parameters, CBPiSensor
|
from cbpi.api import *
|
||||||
|
from cbpi.api.base import CBPiBase
|
||||||
|
from cbpi.api.dataclasses import Kettle, Props, Fermenter
|
||||||
|
|
||||||
@parameters([])
|
@parameters([])
|
||||||
class CustomSensor(CBPiSensor):
|
class CustomSensor(CBPiSensor):
|
||||||
|
@ -23,6 +24,44 @@ class CustomSensor(CBPiSensor):
|
||||||
def get_state(self):
|
def get_state(self):
|
||||||
return dict(value=self.value)
|
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):
|
def setup(cbpi):
|
||||||
'''
|
'''
|
||||||
|
@ -33,3 +72,4 @@ def setup(cbpi):
|
||||||
:return:
|
:return:
|
||||||
'''
|
'''
|
||||||
cbpi.plugin.register("CustomSensor", CustomSensor)
|
cbpi.plugin.register("CustomSensor", CustomSensor)
|
||||||
|
cbpi.plugin.register("DummyPressure", DummyPressure)
|
||||||
|
|
|
@ -24,6 +24,32 @@ class ActorHttpEndpoints():
|
||||||
"""
|
"""
|
||||||
return web.json_response(data=self.controller.get_state())
|
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)
|
@request_mapping(path="/", method="POST", auth_required=False)
|
||||||
async def http_add(self, request):
|
async def http_add(self, request):
|
||||||
|
|
|
@ -12,7 +12,7 @@ class DashBoardHttpEndpoints:
|
||||||
def __init__(self, cbpi):
|
def __init__(self, cbpi):
|
||||||
self.cbpi = cbpi
|
self.cbpi = cbpi
|
||||||
self.controller = cbpi.dashboard
|
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)
|
@request_mapping(path="/{id:\d+}/content", auth_required=False)
|
||||||
|
|
|
@ -77,7 +77,9 @@ class FermentationHttpEndpoints():
|
||||||
description: successful operation
|
description: successful operation
|
||||||
"""
|
"""
|
||||||
data = await request.json()
|
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)
|
response_data = await self.controller.create(fermenter)
|
||||||
return web.json_response(data=response_data.to_dict())
|
return web.json_response(data=response_data.to_dict())
|
||||||
|
|
||||||
|
@ -115,7 +117,9 @@ class FermentationHttpEndpoints():
|
||||||
"""
|
"""
|
||||||
id = request.match_info['id']
|
id = request.match_info['id']
|
||||||
data = await request.json()
|
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())
|
return web.json_response(data=(await self.controller.update(fermenter)).to_dict())
|
||||||
|
|
||||||
@request_mapping(path="/{id}", method="DELETE", auth_required=False)
|
@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"))
|
await self.controller.set_target_temp(id,data.get("temp"))
|
||||||
return web.Response(status=204)
|
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)
|
@request_mapping(path="/{id}/addstep", method="POST", auth_required=False)
|
||||||
async def http_add_step(self, request):
|
async def http_add_step(self, request):
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,10 @@ class CBPiWebSocket:
|
||||||
for ws in self._clients:
|
for ws in self._clients:
|
||||||
async def send_data(ws, data):
|
async def send_data(ws, data):
|
||||||
try:
|
try:
|
||||||
|
try:
|
||||||
|
data['data'].sort(key=lambda x: x.get('name').upper())
|
||||||
|
except:
|
||||||
|
pass
|
||||||
await ws.send_json(data=data, dumps=json_dumps)
|
await ws.send_json(data=data, dumps=json_dumps)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error("Error with client %s: %s" % (ws, str(e)))
|
self.logger.error("Error with client %s: %s" % (ws, str(e)))
|
||||||
|
|
BIN
craftbeerpi.db
BIN
craftbeerpi.db
Binary file not shown.
|
@ -22,3 +22,5 @@ psutil==5.9.0
|
||||||
zipp>=0.5
|
zipp>=0.5
|
||||||
PyInquirer==1.0.3
|
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
|
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
|
import logging
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
from unittest.mock import MagicMock, Mock
|
from aiohttp.test_utils import unittest_run_loop
|
||||||
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
|
from tests.cbpi_config_fixture import CraftBeerPiTestCase
|
||||||
from cbpi.craftbeerpi import CraftBeerPi
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
|
||||||
|
|
||||||
|
|
||||||
class ActorTestCase(AioHTTPTestCase):
|
class ActorTestCase(CraftBeerPiTestCase):
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
@unittest_run_loop
|
@unittest_run_loop
|
||||||
async def test_actor_switch(self):
|
async def test_actor_switch(self):
|
||||||
|
|
||||||
resp = await self.client.post(path="/login", data={"username": "cbpi", "password": "123"})
|
resp = await self.client.post(path="/login", data={"username": "cbpi", "password": "123"})
|
||||||
assert resp.status == 200
|
assert resp.status == 200, "login should be successful"
|
||||||
|
|
||||||
resp = await self.client.request("POST", "/actor/1/on")
|
resp = await self.client.request("POST", "/actor/3CUJte4bkxDMFCtLX8eqsX/on")
|
||||||
assert resp.status == 204
|
assert resp.status == 204, "switching actor on should work"
|
||||||
i = await self.cbpi.actor.get_one(1)
|
i = self.cbpi.actor.find_by_id("3CUJte4bkxDMFCtLX8eqsX")
|
||||||
|
|
||||||
assert i.instance.state is True
|
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
|
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
|
|
||||||
|
|
||||||
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)
|
|
||||||
assert i.instance.state is False
|
assert i.instance.state is False
|
||||||
|
|
||||||
@unittest_run_loop
|
@unittest_run_loop
|
||||||
async def test_crud(self):
|
async def test_crud(self):
|
||||||
data = {
|
data = {
|
||||||
"name": "CustomActor",
|
"name": "SomeActor",
|
||||||
"type": "CustomActor",
|
"power": 100,
|
||||||
"config": {
|
"props": {
|
||||||
"interval": 5
|
},
|
||||||
}
|
"state": False,
|
||||||
|
"type": "DummyActor"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Add new sensor
|
# Add new sensor
|
||||||
|
|
|
@ -1,20 +1,16 @@
|
||||||
import logging
|
import logging
|
||||||
import unittest
|
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')
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
|
||||||
|
|
||||||
|
|
||||||
class CLITest(unittest.TestCase):
|
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):
|
def test_list(self):
|
||||||
list_plugins()
|
cli = CraftBeerPiCli(ConfigFolder("./cbpi-test-config"))
|
||||||
|
cli.plugins_list()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
|
@ -1,54 +1,31 @@
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import aiosqlite
|
from aiohttp.test_utils import unittest_run_loop
|
||||||
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
|
from tests.cbpi_config_fixture import CraftBeerPiTestCase
|
||||||
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
|
|
||||||
|
|
||||||
|
class ConfigTestCase(CraftBeerPiTestCase):
|
||||||
|
|
||||||
@unittest_run_loop
|
@unittest_run_loop
|
||||||
async def test_get(self):
|
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
|
@unittest_run_loop
|
||||||
async def test_set_get(self):
|
async def test_set_get(self):
|
||||||
value = str(time.time())
|
value = 35
|
||||||
|
|
||||||
await self.cbpi.config.set("CBPI_TEST_2", value)
|
await self.cbpi.config.set("steps_cooldown_temp", value)
|
||||||
|
assert self.cbpi.config.get("steps_cooldown_temp", 1) == value
|
||||||
assert self.cbpi.config.get("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")
|
|
||||||
|
|
||||||
@unittest_run_loop
|
@unittest_run_loop
|
||||||
async def test_http_set(self):
|
async def test_http_set(self):
|
||||||
value = str(time.time())
|
value = "Some New Brewery Name"
|
||||||
key = "CBPI_TEST_3"
|
key = "BREWERY_NAME"
|
||||||
await self.cbpi.config.set(key, value)
|
|
||||||
assert self.cbpi.config.get(key, 1) == value
|
|
||||||
|
|
||||||
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 resp.status == 204
|
||||||
assert self.cbpi.config.get(key, -1) == "1"
|
|
||||||
|
assert self.cbpi.config.get(key, -1) == value
|
||||||
|
|
||||||
@unittest_run_loop
|
@unittest_run_loop
|
||||||
async def test_http_get(self):
|
async def test_http_get(self):
|
||||||
|
@ -57,5 +34,5 @@ class ConfigTestCase(AioHTTPTestCase):
|
||||||
|
|
||||||
@unittest_run_loop
|
@unittest_run_loop
|
||||||
async def test_get_default(self):
|
async def test_get_default(self):
|
||||||
value = self.cbpi.config.get("HELLO_WORLD", None)
|
value = self.cbpi.config.get("HELLO_WORLD", "DefaultValue")
|
||||||
assert value == None
|
assert value == "DefaultValue"
|
|
@ -1,18 +1,10 @@
|
||||||
import aiohttp
|
from aiohttp.test_utils import unittest_run_loop
|
||||||
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
|
from tests.cbpi_config_fixture import CraftBeerPiTestCase
|
||||||
|
|
||||||
from cbpi.craftbeerpi import CraftBeerPi
|
from cbpi.craftbeerpi import CraftBeerPi
|
||||||
|
|
||||||
|
|
||||||
class DashboardTestCase(AioHTTPTestCase):
|
class DashboardTestCase(CraftBeerPiTestCase):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def get_application(self):
|
|
||||||
self.cbpi = CraftBeerPi()
|
|
||||||
await self.cbpi.init_serivces()
|
|
||||||
return self.cbpi.app
|
|
||||||
|
|
||||||
@unittest_run_loop
|
@unittest_run_loop
|
||||||
async def test_crud(self):
|
async def test_crud(self):
|
||||||
|
@ -28,56 +20,12 @@ class DashboardTestCase(AioHTTPTestCase):
|
||||||
"config": {}
|
"config": {}
|
||||||
}
|
}
|
||||||
|
|
||||||
resp = await self.client.get(path="/dashboard")
|
resp = await self.client.get(path="/dashboard/current")
|
||||||
assert resp.status == 200
|
assert resp.status == 200
|
||||||
|
|
||||||
# Add new dashboard
|
dashboard_id = await resp.json()
|
||||||
resp = await self.client.post(path="/dashboard/", json=data)
|
|
||||||
assert resp.status == 200
|
|
||||||
|
|
||||||
m = await resp.json()
|
|
||||||
dashboard_id = m["id"]
|
|
||||||
|
|
||||||
# Get dashboard
|
|
||||||
resp = await self.client.get(path="/dashboard/%s" % dashboard_id)
|
|
||||||
assert resp.status == 200
|
|
||||||
|
|
||||||
m2 = await resp.json()
|
|
||||||
dashboard_id = m2["id"]
|
|
||||||
|
|
||||||
# Update dashboard
|
|
||||||
resp = await self.client.put(path="/dashboard/%s" % dashboard_id, json=m)
|
|
||||||
assert resp.status == 200
|
|
||||||
|
|
||||||
# Add dashboard content
|
# Add dashboard content
|
||||||
dashboard_content["dbid"] = dashboard_id
|
dashboard_content["dbid"] = dashboard_id
|
||||||
resp = await self.client.post(path="/dashboard/%s/content" % dashboard_id, json=dashboard_content)
|
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
|
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)
|
|
|
@ -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()
|
patcher.start()
|
||||||
import RPi.GPIO as GPIO
|
import RPi.GPIO as GPIO
|
||||||
|
|
||||||
|
|
||||||
class HelloWorld(object):
|
|
||||||
|
|
||||||
def test(self, a):
|
|
||||||
return a
|
|
||||||
|
|
||||||
|
|
||||||
class TestSwitch(unittest.TestCase):
|
class TestSwitch(unittest.TestCase):
|
||||||
|
|
||||||
GPIO_NUM = 22
|
GPIO_NUM = 22
|
||||||
|
@ -36,14 +29,3 @@ class TestSwitch(unittest.TestCase):
|
||||||
def test_switch_without_scheduler_starts_disabled(self, patched_output):
|
def test_switch_without_scheduler_starts_disabled(self, patched_output):
|
||||||
GPIO.output(self.GPIO_NUM, GPIO.LOW)
|
GPIO.output(self.GPIO_NUM, GPIO.LOW)
|
||||||
patched_output.assert_called_once_with(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)
|
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,8 @@
|
||||||
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 IndexTestCase(AioHTTPTestCase):
|
class IndexTestCase(CraftBeerPiTestCase):
|
||||||
|
|
||||||
async def get_application(self):
|
|
||||||
self.cbpi = CraftBeerPi()
|
|
||||||
await self.cbpi.init_serivces()
|
|
||||||
return self.cbpi.app
|
|
||||||
|
|
||||||
@unittest_run_loop
|
@unittest_run_loop
|
||||||
async def test_index(self):
|
async def test_index(self):
|
||||||
|
|
|
@ -1,52 +1,16 @@
|
||||||
import asyncio
|
from aiohttp.test_utils import unittest_run_loop
|
||||||
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
|
from tests.cbpi_config_fixture import CraftBeerPiTestCase
|
||||||
from cbpi.craftbeerpi import CraftBeerPi
|
|
||||||
|
|
||||||
|
|
||||||
class KettleTestCase(AioHTTPTestCase):
|
class KettleTestCase(CraftBeerPiTestCase):
|
||||||
|
|
||||||
|
|
||||||
async def get_application(self):
|
|
||||||
self.cbpi = CraftBeerPi()
|
|
||||||
await self.cbpi.init_serivces()
|
|
||||||
return self.cbpi.app
|
|
||||||
|
|
||||||
@unittest_run_loop
|
@unittest_run_loop
|
||||||
async def test_get(self):
|
async def test_get(self):
|
||||||
|
|
||||||
resp = await self.client.request("GET", "/kettle")
|
resp = await self.client.request("GET", "/kettle")
|
||||||
assert resp.status == 200
|
assert resp.status == 200
|
||||||
print(await resp.json())
|
kettle = resp.json()
|
||||||
|
assert kettle != None
|
||||||
@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
|
|
||||||
|
|
||||||
|
|
||||||
@unittest_run_loop
|
@unittest_run_loop
|
||||||
async def test_crud(self):
|
async def test_crud(self):
|
||||||
|
@ -69,21 +33,21 @@ class KettleTestCase(AioHTTPTestCase):
|
||||||
|
|
||||||
m = await resp.json()
|
m = await resp.json()
|
||||||
|
|
||||||
sensor_id = m["id"]
|
kettle_id = m["id"]
|
||||||
print("KETTLE", m["id"], m)
|
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
|
assert resp.status == 200
|
||||||
|
|
||||||
m2 = await resp.json()
|
# Set Kettle target temp
|
||||||
sensor_id = m2["id"]
|
resp = await self.client.post(path="/kettle/%s/target_temp" % kettle_id, json={"temp":75})
|
||||||
|
assert resp.status == 204
|
||||||
# Update Sensor
|
|
||||||
resp = await self.client.put(path="/kettle/%s" % sensor_id, json=m)
|
|
||||||
assert resp.status == 200
|
|
||||||
|
|
||||||
# # Delete Sensor
|
# # 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
|
assert resp.status == 204
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,16 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import glob
|
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 LoggerTestCase(CraftBeerPiTestCase):
|
||||||
|
|
||||||
|
|
||||||
class UtilsTestCase(AioHTTPTestCase):
|
|
||||||
|
|
||||||
async def get_application(self):
|
|
||||||
self.cbpi = CraftBeerPi()
|
|
||||||
await self.cbpi.init_serivces()
|
|
||||||
return self.cbpi.app
|
|
||||||
|
|
||||||
@unittest_run_loop
|
@unittest_run_loop
|
||||||
async def test_log_data(self):
|
async def test_log_data(self):
|
||||||
|
|
||||||
|
os.makedirs("./logs", exist_ok=True)
|
||||||
log_name = "test"
|
log_name = "test"
|
||||||
#clear all logs
|
#clear all logs
|
||||||
self.cbpi.log.clear_log(log_name)
|
self.cbpi.log.clear_log(log_name)
|
||||||
|
|
|
@ -1,16 +1,8 @@
|
||||||
import aiohttp
|
from aiohttp.test_utils import unittest_run_loop
|
||||||
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
|
from tests.cbpi_config_fixture import CraftBeerPiTestCase
|
||||||
|
|
||||||
from cbpi.craftbeerpi import CraftBeerPi
|
|
||||||
|
|
||||||
|
|
||||||
class NotificationTestCase(AioHTTPTestCase):
|
class NotificationTestCase(CraftBeerPiTestCase):
|
||||||
|
|
||||||
async def get_application(self):
|
|
||||||
self.cbpi = CraftBeerPi()
|
|
||||||
await self.cbpi.init_serivces()
|
|
||||||
return self.cbpi.app
|
|
||||||
|
|
||||||
|
|
||||||
@unittest_run_loop
|
@unittest_run_loop
|
||||||
async def test_actor_switch(self):
|
async def test_actor_switch(self):
|
||||||
|
|
|
@ -1,14 +1,8 @@
|
||||||
import asyncio
|
from aiohttp.test_utils import unittest_run_loop
|
||||||
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
|
from tests.cbpi_config_fixture import CraftBeerPiTestCase
|
||||||
from cbpi.craftbeerpi import CraftBeerPi
|
|
||||||
|
|
||||||
|
|
||||||
class SensorTestCase(AioHTTPTestCase):
|
class SensorTestCase(CraftBeerPiTestCase):
|
||||||
|
|
||||||
async def get_application(self):
|
|
||||||
self.cbpi = CraftBeerPi()
|
|
||||||
await self.cbpi.init_serivces()
|
|
||||||
return self.cbpi.app
|
|
||||||
|
|
||||||
@unittest_run_loop
|
@unittest_run_loop
|
||||||
async def test_crud(self):
|
async def test_crud(self):
|
||||||
|
@ -28,12 +22,11 @@ class SensorTestCase(AioHTTPTestCase):
|
||||||
m = await resp.json()
|
m = await resp.json()
|
||||||
sensor_id = m["id"]
|
sensor_id = m["id"]
|
||||||
|
|
||||||
# Get sensor
|
# Get sensor value
|
||||||
resp = await self.client.get(path="/sensor/%s"% sensor_id)
|
resp = await self.client.get(path="/sensor/%s"% sensor_id)
|
||||||
assert resp.status == 200
|
assert resp.status == 200
|
||||||
|
|
||||||
m2 = await resp.json()
|
m2 = await resp.json()
|
||||||
sensor_id = m2["id"]
|
|
||||||
|
|
||||||
# Update Sensor
|
# Update Sensor
|
||||||
resp = await self.client.put(path="/sensor/%s" % sensor_id, json=m)
|
resp = await self.client.put(path="/sensor/%s" % sensor_id, json=m)
|
||||||
|
|
|
@ -1,23 +1,13 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
|
from aiohttp.test_utils import unittest_run_loop
|
||||||
from cbpi.craftbeerpi import CraftBeerPi
|
from tests.cbpi_config_fixture import CraftBeerPiTestCase
|
||||||
|
|
||||||
|
class StepTestCase(CraftBeerPiTestCase):
|
||||||
class StepTestCase(AioHTTPTestCase):
|
|
||||||
|
|
||||||
async def get_application(self):
|
|
||||||
self.cbpi = CraftBeerPi()
|
|
||||||
await self.cbpi.init_serivces()
|
|
||||||
return self.cbpi.app
|
|
||||||
|
|
||||||
@unittest_run_loop
|
@unittest_run_loop
|
||||||
async def test_get(self):
|
async def test_get(self):
|
||||||
|
|
||||||
resp = await self.client.request("GET", "/step")
|
resp = await self.client.request("GET", "/step2")
|
||||||
print(resp)
|
|
||||||
assert resp.status == 200
|
|
||||||
|
|
||||||
resp = await self.client.request("GET", "/step/types")
|
|
||||||
print(resp)
|
print(resp)
|
||||||
assert resp.status == 200
|
assert resp.status == 200
|
||||||
|
|
||||||
|
@ -31,26 +21,19 @@ class StepTestCase(AioHTTPTestCase):
|
||||||
}
|
}
|
||||||
|
|
||||||
# Add new step
|
# 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
|
assert resp.status == 200
|
||||||
|
|
||||||
m = await resp.json()
|
m = await resp.json()
|
||||||
print("Step", m)
|
print("Step", m)
|
||||||
sensor_id = m["id"]
|
sensor_id = m["id"]
|
||||||
|
|
||||||
# Get sensor
|
# Update step
|
||||||
resp = await self.client.get(path="/step/%s" % sensor_id)
|
resp = await self.client.put(path="/step2/%s" % sensor_id, json=m)
|
||||||
assert resp.status == 200
|
assert resp.status == 200
|
||||||
|
|
||||||
m2 = await resp.json()
|
# # Delete step
|
||||||
sensor_id = m2["id"]
|
resp = await self.client.delete(path="/step2/%s" % sensor_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)
|
|
||||||
assert resp.status == 204
|
assert resp.status == 204
|
||||||
|
|
||||||
def create_wait_callback(self, topic):
|
def create_wait_callback(self, topic):
|
||||||
|
@ -67,33 +50,3 @@ class StepTestCase(AioHTTPTestCase):
|
||||||
|
|
||||||
if future in done:
|
if future in done:
|
||||||
pass
|
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())
|
|
||||||
|
|
|
@ -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 aiohttp.test_utils import unittest_run_loop
|
||||||
|
from tests.cbpi_config_fixture import CraftBeerPiTestCase
|
||||||
from cbpi.craftbeerpi import CraftBeerPi
|
|
||||||
|
|
||||||
|
|
||||||
class IndexTestCase(AioHTTPTestCase):
|
class IndexTestCase(CraftBeerPiTestCase):
|
||||||
|
|
||||||
async def get_application(self):
|
|
||||||
self.cbpi = CraftBeerPi()
|
|
||||||
await self.cbpi.init_serivces()
|
|
||||||
return self.cbpi.app
|
|
||||||
|
|
||||||
@unittest_run_loop
|
@unittest_run_loop
|
||||||
async def test_endpoints(self):
|
async def test_endpoints(self):
|
||||||
|
|
||||||
|
|
||||||
# Test Index Page
|
# Test Index Page
|
||||||
resp = await self.client.post(path="/system/restart")
|
resp = await self.client.post(path="/system/restart")
|
||||||
assert resp.status == 200
|
assert resp.status == 200
|
||||||
|
@ -21,9 +13,3 @@ class IndexTestCase(AioHTTPTestCase):
|
||||||
resp = await self.client.post(path="/system/shutdown")
|
resp = await self.client.post(path="/system/shutdown")
|
||||||
assert resp.status == 200
|
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
|
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):
|
# assert count_step_done == 4
|
||||||
self.cbpi = CraftBeerPi()
|
|
||||||
await self.cbpi.init_serivces()
|
|
||||||
return self.cbpi.app
|
|
||||||
|
|
||||||
|
# @unittest_run_loop
|
||||||
|
# async def test_wrong_format(self):
|
||||||
|
|
||||||
@unittest_run_loop
|
# async with self.client.ws_connect('/ws') as ws:
|
||||||
async def test_brewing_process(self):
|
# 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
|
# else:
|
||||||
async with self.client.ws_connect('/ws') as ws:
|
# raise Exception()
|
||||||
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()
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue