mirror of
https://github.com/PiBrewing/craftbeerpi4.git
synced 2024-11-24 16:08:11 +01:00
Merge pull request #87 from papauorg/feature/development-environment-upstream
Provide preconfigured dev-environment and automated tests -> some fixes still required for femrneter recipes and config path
This commit is contained in:
commit
46a589c463
75 changed files with 1445 additions and 988 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/main.zip -L -o /opt/downloads/cbpi-ui.zip \
|
||||
&& pip3 install --no-cache-dir /opt/downloads/cbpi-ui.zip \
|
||||
&& rm -rf /opt/downloads
|
3
.devcontainer/cbpi-dev-config/actor.json
Normal file
3
.devcontainer/cbpi-dev-config/actor.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"data": []
|
||||
}
|
6
.devcontainer/cbpi-dev-config/chromium.desktop
Normal file
6
.devcontainer/cbpi-dev-config/chromium.desktop
Normal file
|
@ -0,0 +1,6 @@
|
|||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Chromium
|
||||
Comment=Chromium Webbrowser
|
||||
NoDisplay=false
|
||||
Exec=chromium-browser --noerrordialogs --disable-session-crashed-bubble --disable-infobars --force-device-scale-factor=1.00 --start-fullscreen "http://localhost:8000"
|
335
.devcontainer/cbpi-dev-config/config.json
Normal file
335
.devcontainer/cbpi-dev-config/config.json
Normal file
|
@ -0,0 +1,335 @@
|
|||
{
|
||||
"AUTHOR": {
|
||||
"description": "Author",
|
||||
"name": "AUTHOR",
|
||||
"options": null,
|
||||
"type": "string",
|
||||
"value": "John Doe"
|
||||
},
|
||||
"AddMashInStep": {
|
||||
"description": "Add MashIn Step automatically if not defined in recipe",
|
||||
"name": "AddMashInStep",
|
||||
"options": [
|
||||
{
|
||||
"label": "Yes",
|
||||
"value": "Yes"
|
||||
},
|
||||
{
|
||||
"label": "No",
|
||||
"value": "No"
|
||||
}
|
||||
],
|
||||
"type": "select",
|
||||
"value": "Yes"
|
||||
},
|
||||
"AutoMode": {
|
||||
"description": "Use AutoMode in steps",
|
||||
"name": "AutoMode",
|
||||
"options": [
|
||||
{
|
||||
"label": "Yes",
|
||||
"value": "Yes"
|
||||
},
|
||||
{
|
||||
"label": "No",
|
||||
"value": "No"
|
||||
}
|
||||
],
|
||||
"type": "select",
|
||||
"value": "Yes"
|
||||
},
|
||||
"BREWERY_NAME": {
|
||||
"description": "Brewery Name",
|
||||
"name": "BREWERY_NAME",
|
||||
"options": null,
|
||||
"type": "string",
|
||||
"value": "CraftBeerPi Brewery"
|
||||
},
|
||||
"BoilKettle": {
|
||||
"description": "Define Kettle that is used for Boil, Whirlpool and Cooldown. If not selected, MASH_TUN will be used",
|
||||
"name": "BoilKettle",
|
||||
"options": null,
|
||||
"type": "kettle",
|
||||
"value": ""
|
||||
},
|
||||
"CBPI_TEST_3": {
|
||||
"description": "test",
|
||||
"name": "CBPI_TEST_3",
|
||||
"options": null,
|
||||
"type": "string",
|
||||
"value": "1"
|
||||
},
|
||||
"CSVLOGFILES": {
|
||||
"description": "Write sensor data to csv logfiles",
|
||||
"name": "CSVLOGFILES",
|
||||
"options": [
|
||||
{
|
||||
"label": "Yes",
|
||||
"value": "Yes"
|
||||
},
|
||||
{
|
||||
"label": "No",
|
||||
"value": "No"
|
||||
}
|
||||
],
|
||||
"type": "select",
|
||||
"value": "Yes"
|
||||
},
|
||||
"INFLUXDB": {
|
||||
"description": "Write sensor data to influxdb",
|
||||
"name": "INFLUXDB",
|
||||
"options": [
|
||||
{
|
||||
"label": "Yes",
|
||||
"value": "Yes"
|
||||
},
|
||||
{
|
||||
"label": "No",
|
||||
"value": "No"
|
||||
}
|
||||
],
|
||||
"type": "select",
|
||||
"value": "No"
|
||||
},
|
||||
"INFLUXDBADDR": {
|
||||
"description": "IP Address of your influxdb server (If INFLUXDBCLOUD set to Yes use URL Address of your influxdb cloud server)",
|
||||
"name": "INFLUXDBADDR",
|
||||
"options": null,
|
||||
"type": "string",
|
||||
"value": "localhost"
|
||||
},
|
||||
"INFLUXDBCLOUD": {
|
||||
"description": "Write sensor data to influxdb cloud (INFLUXDB must set to Yes)",
|
||||
"name": "INFLUXDBCLOUD",
|
||||
"options": [
|
||||
{
|
||||
"label": "Yes",
|
||||
"value": "Yes"
|
||||
},
|
||||
{
|
||||
"label": "No",
|
||||
"value": "No"
|
||||
}
|
||||
],
|
||||
"type": "select",
|
||||
"value": "No"
|
||||
},
|
||||
"INFLUXDBNAME": {
|
||||
"description": "Name of your influxdb database name (If INFLUXDBCLOUD set to Yes use bucket of your influxdb cloud database)",
|
||||
"name": "INFLUXDBNAME",
|
||||
"options": null,
|
||||
"type": "string",
|
||||
"value": "cbpi4"
|
||||
},
|
||||
"INFLUXDBPORT": {
|
||||
"description": "Port of your influxdb server",
|
||||
"name": "INFLUXDBPORT",
|
||||
"options": null,
|
||||
"type": "string",
|
||||
"value": "8086"
|
||||
},
|
||||
"INFLUXDBPWD": {
|
||||
"description": "Password for your influxdb database (only if required)(If INFLUXDBCLOUD set to Yes use token of your influxdb cloud database)",
|
||||
"name": "INFLUXDBPWD",
|
||||
"options": null,
|
||||
"type": "string",
|
||||
"value": " "
|
||||
},
|
||||
"INFLUXDBUSER": {
|
||||
"description": "User name for your influxdb database (only if required)(If INFLUXDBCLOUD set to Yes use organisation of your influxdb cloud database)",
|
||||
"name": "INFLUXDBUSER",
|
||||
"options": null,
|
||||
"type": "string",
|
||||
"value": " "
|
||||
},
|
||||
"MASH_TUN": {
|
||||
"description": "Default Mash Tun",
|
||||
"name": "MASH_TUN",
|
||||
"options": null,
|
||||
"type": "kettle",
|
||||
"value": ""
|
||||
},
|
||||
"MQTTUpdate": {
|
||||
"description": "Forced MQTT Update frequency in s for Kettle and Fermenter (no changes in payload required). Restart required after change",
|
||||
"name": "MQTTUpdate",
|
||||
"options": [
|
||||
{
|
||||
"label": "30",
|
||||
"value": 30
|
||||
},
|
||||
{
|
||||
"label": "60",
|
||||
"value": 60
|
||||
},
|
||||
{
|
||||
"label": "120",
|
||||
"value": 120
|
||||
},
|
||||
{
|
||||
"label": "300",
|
||||
"value": 300
|
||||
},
|
||||
{
|
||||
"label": "Never",
|
||||
"value": 0
|
||||
}
|
||||
],
|
||||
"type": "select",
|
||||
"value": 0
|
||||
},
|
||||
"RECIPE_CREATION_PATH": {
|
||||
"description": "API path to creation plugin. Default: upload . CHANGE ONLY IF USING A RECIPE CREATION PLUGIN",
|
||||
"name": "RECIPE_CREATION_PATH",
|
||||
"options": null,
|
||||
"type": "string",
|
||||
"value": "upload"
|
||||
},
|
||||
"TEMP_UNIT": {
|
||||
"description": "Temperature Unit",
|
||||
"name": "TEMP_UNIT",
|
||||
"options": [
|
||||
{
|
||||
"label": "C",
|
||||
"value": "C"
|
||||
},
|
||||
{
|
||||
"label": "F",
|
||||
"value": "F"
|
||||
}
|
||||
],
|
||||
"type": "select",
|
||||
"value": "C"
|
||||
},
|
||||
"brewfather_api_key": {
|
||||
"description": "Brewfather API Key",
|
||||
"name": "brewfather_api_key",
|
||||
"options": null,
|
||||
"type": "string",
|
||||
"value": ""
|
||||
},
|
||||
"brewfather_user_id": {
|
||||
"description": "Brewfather User ID",
|
||||
"name": "brewfather_user_id",
|
||||
"options": null,
|
||||
"type": "string",
|
||||
"value": ""
|
||||
},
|
||||
"current_dashboard_number": {
|
||||
"description": "Number of current Dashboard",
|
||||
"name": "current_dashboard_number",
|
||||
"options": null,
|
||||
"type": "number",
|
||||
"value": 1
|
||||
},
|
||||
"max_dashboard_number": {
|
||||
"description": "Max Number of Dashboards",
|
||||
"name": "max_dashboard_number",
|
||||
"options": [
|
||||
{
|
||||
"label": "1",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"label": "2",
|
||||
"value": 2
|
||||
},
|
||||
{
|
||||
"label": "3",
|
||||
"value": 3
|
||||
},
|
||||
{
|
||||
"label": "4",
|
||||
"value": 4
|
||||
},
|
||||
{
|
||||
"label": "5",
|
||||
"value": 5
|
||||
},
|
||||
{
|
||||
"label": "6",
|
||||
"value": 6
|
||||
},
|
||||
{
|
||||
"label": "7",
|
||||
"value": 7
|
||||
},
|
||||
{
|
||||
"label": "8",
|
||||
"value": 8
|
||||
},
|
||||
{
|
||||
"label": "9",
|
||||
"value": 9
|
||||
},
|
||||
{
|
||||
"label": "10",
|
||||
"value": 10
|
||||
}
|
||||
],
|
||||
"type": "select",
|
||||
"value": 4
|
||||
},
|
||||
"steps_boil": {
|
||||
"description": "Boil step type",
|
||||
"name": "steps_boil",
|
||||
"options": null,
|
||||
"type": "step",
|
||||
"value": "BoilStep"
|
||||
},
|
||||
"steps_boil_temp": {
|
||||
"description": "Default Boil Temperature for Recipe Creation",
|
||||
"name": "steps_boil_temp",
|
||||
"options": null,
|
||||
"type": "number",
|
||||
"value": "99"
|
||||
},
|
||||
"steps_cooldown": {
|
||||
"description": "Cooldown step type",
|
||||
"name": "steps_cooldown",
|
||||
"options": null,
|
||||
"type": "step",
|
||||
"value": "CooldownStep"
|
||||
},
|
||||
"steps_cooldown_actor": {
|
||||
"description": "Actor to trigger cooldown water on and off (default: None)",
|
||||
"name": "steps_cooldown_actor",
|
||||
"options": null,
|
||||
"type": "actor",
|
||||
"value": ""
|
||||
},
|
||||
"steps_cooldown_sensor": {
|
||||
"description": "Alternative Sensor to monitor temperature durring cooldown (if not selected, Kettle Sensor will be used)",
|
||||
"name": "steps_cooldown_sensor",
|
||||
"options": null,
|
||||
"type": "sensor",
|
||||
"value": ""
|
||||
},
|
||||
"steps_cooldown_temp": {
|
||||
"description": "Cooldown temp will send notification when this temeprature is reached",
|
||||
"name": "steps_cooldown_temp",
|
||||
"options": null,
|
||||
"type": "number",
|
||||
"value": "20"
|
||||
},
|
||||
"steps_mash": {
|
||||
"description": "Mash step type",
|
||||
"name": "steps_mash",
|
||||
"options": null,
|
||||
"type": "step",
|
||||
"value": "MashStep"
|
||||
},
|
||||
"steps_mashin": {
|
||||
"description": "MashIn step type",
|
||||
"name": "steps_mashin",
|
||||
"options": null,
|
||||
"type": "step",
|
||||
"value": "MashInStep"
|
||||
},
|
||||
"steps_mashout": {
|
||||
"description": "MashOut step type",
|
||||
"name": "steps_mashout",
|
||||
"options": null,
|
||||
"type": "step",
|
||||
"value": "NotificationStep"
|
||||
}
|
||||
}
|
20
.devcontainer/cbpi-dev-config/config.yaml
Normal file
20
.devcontainer/cbpi-dev-config/config.yaml
Normal file
|
@ -0,0 +1,20 @@
|
|||
|
||||
name: CraftBeerPi
|
||||
version: 4.0.8
|
||||
|
||||
index_url: /cbpi_ui/static/index.html
|
||||
|
||||
port: 8000
|
||||
|
||||
mqtt: true
|
||||
mqtt_host: mqtt
|
||||
mqtt_port: 1883
|
||||
mqtt_username: craftbeerpi
|
||||
mqtt_password: cbpiSuperSecMq77!
|
||||
|
||||
username: cbpi
|
||||
password: 123
|
||||
|
||||
plugins:
|
||||
- cbpi4ui
|
||||
|
9
.devcontainer/cbpi-dev-config/craftbeerpi.service
Normal file
9
.devcontainer/cbpi-dev-config/craftbeerpi.service
Normal file
|
@ -0,0 +1,9 @@
|
|||
[Unit]
|
||||
Description=Craftbeer Pi
|
||||
|
||||
[Service]
|
||||
WorkingDirectory=/home/pi
|
||||
ExecStart=/usr/local/bin/cbpi start
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"elements": []
|
||||
}
|
3
.devcontainer/cbpi-dev-config/fermenter_data.json
Normal file
3
.devcontainer/cbpi-dev-config/fermenter_data.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"data": []
|
||||
}
|
3
.devcontainer/cbpi-dev-config/kettle.json
Normal file
3
.devcontainer/cbpi-dev-config/kettle.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"data": []
|
||||
}
|
3
.devcontainer/cbpi-dev-config/sensor.json
Normal file
3
.devcontainer/cbpi-dev-config/sensor.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"data": []
|
||||
}
|
6
.devcontainer/cbpi-dev-config/step_data.json
Normal file
6
.devcontainer/cbpi-dev-config/step_data.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"basic": {
|
||||
"name": ""
|
||||
},
|
||||
"steps": []
|
||||
}
|
5
.devcontainer/createMqttUser.sh
Normal file
5
.devcontainer/createMqttUser.sh
Normal file
|
@ -0,0 +1,5 @@
|
|||
#!/bin/bash
|
||||
|
||||
USER=craftbeerpi
|
||||
PASSWORD=craftbeerpi
|
||||
docker run -it --rm -v "$(pwd)/mosquitto/config/mosquitto.passwd:/opt/passwdfile" eclipse-mosquitto:2 mosquitto_passwd -b /opt/passwdfile $USER $PASSWORD
|
51
.devcontainer/devcontainer.json
Normal file
51
.devcontainer/devcontainer.json
Normal file
|
@ -0,0 +1,51 @@
|
|||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
|
||||
// https://github.com/microsoft/vscode-dev-containers/tree/v0.177.0/containers/docker-existing-docker-compose
|
||||
// If you want to run as a non-root user in the container, see .devcontainer/docker-compose.yml.
|
||||
{
|
||||
"name": "CraftBeerPi4",
|
||||
|
||||
// Update the 'dockerComposeFile' list if you have more compose files or use different names.
|
||||
// The .devcontainer/docker-compose.yml file contains any overrides you need/want to make.
|
||||
"dockerComposeFile": [
|
||||
"docker-compose.dev.yml"
|
||||
],
|
||||
|
||||
// The 'service' property is the name of the service for the container that VS Code should
|
||||
// use. Update this value and .devcontainer/docker-compose.yml to the real service name.
|
||||
"service": "craftbeerpi4-development",
|
||||
|
||||
// The optional 'workspaceFolder' property is the path VS Code should open by default when
|
||||
// connected. This is typically a file mount in .devcontainer/docker-compose.yml
|
||||
"workspaceFolder": "/workspace",
|
||||
|
||||
// Set *default* container specific settings.json values on container create.
|
||||
"settings": {
|
||||
//"terminal.integrated.shell.linux": null
|
||||
},
|
||||
|
||||
// Add the IDs of extensions you want installed when the container is created.
|
||||
"extensions": [
|
||||
"ms-python.python",
|
||||
"ms-azuretools.vscode-docker",
|
||||
"editorconfig.editorconfig",
|
||||
"eamodio.gitlens"
|
||||
],
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
"forwardPorts": [
|
||||
"craftbeerpi4-development:8000",
|
||||
"mqtt-explorer:4000"
|
||||
],
|
||||
|
||||
// Uncomment the next line if you want start specific services in your Docker Compose config.
|
||||
// "runServices": [],
|
||||
|
||||
// Uncomment the next line if you want to keep your containers running after VS Code shuts down.
|
||||
"shutdownAction": "stopCompose",
|
||||
|
||||
// Uncomment the next line to run commands after the container is created - for example installing curl.
|
||||
//"postCreateCommand": "pip3 install -r ./requirements.txt",
|
||||
|
||||
// Uncomment to connect as a non-root user if you've added one. See https://aka.ms/vscode-remote/containers/non-root.
|
||||
"remoteUser": "vscode"
|
||||
}
|
30
.devcontainer/docker-compose.dev.yml
Normal file
30
.devcontainer/docker-compose.dev.yml
Normal file
|
@ -0,0 +1,30 @@
|
|||
version: '3.4'
|
||||
services:
|
||||
mqtt:
|
||||
image: eclipse-mosquitto:2
|
||||
volumes:
|
||||
- "./mosquitto/config:/mosquitto/config"
|
||||
restart: unless-stopped
|
||||
|
||||
craftbeerpi4-development:
|
||||
build:
|
||||
context: ../
|
||||
dockerfile: .devcontainer/Dockerfile
|
||||
command: /bin/sh -c "while sleep 1000; do :; done"
|
||||
user: vscode
|
||||
depends_on:
|
||||
- mqtt
|
||||
volumes:
|
||||
# Update this to wherever you want VS Code to mount the folder of your project
|
||||
- ../:/workspace:cached
|
||||
|
||||
mqtt-explorer:
|
||||
image: smeagolworms4/mqtt-explorer
|
||||
environment:
|
||||
HTTP_PORT: 4000
|
||||
CONFIG_PATH: /mqtt-explorer/config
|
||||
volumes:
|
||||
- "./mqtt-explorer/config:/mqtt-explorer/config"
|
||||
depends_on:
|
||||
- mqtt
|
||||
restart: unless-stopped
|
10
.devcontainer/mosquitto/config/mosquitto.conf
Normal file
10
.devcontainer/mosquitto/config/mosquitto.conf
Normal file
|
@ -0,0 +1,10 @@
|
|||
persistence true
|
||||
persistence_location /mosquitto/data
|
||||
|
||||
log_dest file /mosquitto/log/mosquitto.log
|
||||
log_dest stdout
|
||||
|
||||
password_file /mosquitto/config/mosquitto.passwd
|
||||
allow_anonymous false
|
||||
|
||||
port 1883
|
2
.devcontainer/mosquitto/config/mosquitto.passwd
Normal file
2
.devcontainer/mosquitto/config/mosquitto.passwd
Normal file
|
@ -0,0 +1,2 @@
|
|||
craftbeerpi:$7$101$cRIEIwJ9L/+TAFF1$lxT+v9SisokWaRBgB/Scut7DaotH4RMgzHttYHhwuy6m5yatSoac7bwrkztoQ7raNehBhKt/A4VVejnzozdxXA==
|
||||
mqtt-explorer:$7$101$SFFKvbIBVXFFAIBp$Pgue6DaAfcuhegjEqtTjf+WWgNZ8geiv1/3fXqmJ0APmd0L80wNTSrEhnFdJmHvi0/vW6V9bVKPJfVRDIjPxCw==
|
31
.devcontainer/mqtt-explorer/config/settings.json
Normal file
31
.devcontainer/mqtt-explorer/config/settings.json
Normal file
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"ConnectionManager_connections": {
|
||||
"mqtt.eclipse.org": {
|
||||
"configVersion": 1,
|
||||
"certValidation": true,
|
||||
"clientId": "mqtt-explorer-8eb042b9",
|
||||
"id": "mqtt.eclipse.org",
|
||||
"name": "CraftBeerPi MQTT Explorer",
|
||||
"encryption": false,
|
||||
"subscriptions": [
|
||||
{
|
||||
"topic": "#",
|
||||
"qos": 0
|
||||
},
|
||||
{
|
||||
"topic": "$SYS/#",
|
||||
"qos": 0
|
||||
}
|
||||
],
|
||||
"type": "mqtt",
|
||||
"host": "mqtt",
|
||||
"port": 1883,
|
||||
"protocol": "mqtt",
|
||||
"changeSet": {
|
||||
"password": "mqtt-explorer"
|
||||
},
|
||||
"username": "mqtt-explorer",
|
||||
"password": "mqtt-explorer"
|
||||
}
|
||||
}
|
||||
}
|
7
.github/workflows/build.yml
vendored
7
.github/workflows/build.yml
vendored
|
@ -27,8 +27,11 @@ jobs:
|
|||
- name: Clean
|
||||
run: python setup.py clean --all
|
||||
|
||||
# - name: Run tests
|
||||
# run: python -m unittest tests
|
||||
- name: Install Requirements
|
||||
run: pip3 install -r requirements.txt
|
||||
|
||||
- name: Run tests
|
||||
run: coverage run --source cbpi -m pytest tests
|
||||
|
||||
- name: Build source distribution package for CraftBeerPi
|
||||
run: python setup.py sdist
|
||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -14,6 +14,6 @@ node_modules
|
|||
.vscode
|
||||
.venv*
|
||||
.DS_Store
|
||||
.vscode/
|
||||
config/*
|
||||
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
|
||||
}
|
14
README.md
14
README.md
|
@ -21,7 +21,19 @@ in the documentation, that can be found here: [gitbook.io](https://openbrewing.g
|
|||
Plugins extend the base functionality of CraftBeerPi 4.
|
||||
You can find a list of available plugins [here](https://openbrewing.gitbook.io/craftbeerpi4_support/master/plugin-installation#plugin-list).
|
||||
|
||||
## 🧑🤝🧑 Contributers
|
||||
## 🧑🤝🧑 Contribute
|
||||
You want to help develop CraftBeerPi4? To get you quickly stated, this repository comes with a preconfigured
|
||||
development environment. To be able to use this environment you need 2 things installed on your computer:
|
||||
|
||||
- docker
|
||||
- visual studio code (vscode)
|
||||
|
||||
To start developing clone this repository, open the folder in vscode and use the _development container_ feature. The command is called _Reopen in container_. Please note that this quick start setup does not work if you want to develop directly on a 32bit raspberry pi os because docker is only available for 64bit arm plattform. Please use the regular development setup for that.
|
||||
|
||||
For a more detailed description of a development setup without the _development container_ feature see the documentation page:
|
||||
[gitbook.io](https://openbrewing.gitbook.io/craftbeerpi4_support/)
|
||||
|
||||
### Contributors
|
||||
Thanks to all the people who have contributed
|
||||
|
||||
[![contributors](https://contributors-img.web.app/image?repo=craftbeerpi/craftbeerpi4)](https://github.com/craftbeerpi/craftbeerpi4/graphs/contributors)
|
||||
|
|
326
cbpi/cli.py
326
cbpi/cli.py
|
@ -1,103 +1,45 @@
|
|||
import argparse
|
||||
import datetime
|
||||
import logging
|
||||
import subprocess
|
||||
import sys
|
||||
import re
|
||||
import requests
|
||||
import yaml
|
||||
from cbpi.configFolder import ConfigFolder
|
||||
from cbpi.utils.utils import load_config
|
||||
from zipfile import ZipFile
|
||||
from cbpi.craftbeerpi import CraftBeerPi
|
||||
import os
|
||||
import platform
|
||||
import pathlib
|
||||
import pkgutil
|
||||
import shutil
|
||||
import yaml
|
||||
import click
|
||||
from subprocess import call
|
||||
import zipfile
|
||||
from colorama import Fore, Back, Style
|
||||
from importlib import import_module
|
||||
import importlib
|
||||
from jinja2 import Template
|
||||
from importlib_metadata import metadata, version
|
||||
from importlib_metadata import metadata
|
||||
from tabulate import tabulate
|
||||
from PyInquirer import prompt, print_json
|
||||
|
||||
def create_config_file():
|
||||
if os.path.exists(os.path.join(".", 'config', "config.yaml")) is False:
|
||||
srcfile = os.path.join(os.path.dirname(__file__), "config", "config.yaml")
|
||||
destfile = os.path.join(".", 'config')
|
||||
shutil.copy(srcfile, destfile)
|
||||
|
||||
if os.path.exists(os.path.join(".", 'config', "actor.json")) is False:
|
||||
srcfile = os.path.join(os.path.dirname(__file__), "config", "actor.json")
|
||||
destfile = os.path.join(".", 'config')
|
||||
shutil.copy(srcfile, destfile)
|
||||
class CraftBeerPiCli():
|
||||
def __init__(self, config) -> None:
|
||||
self.config = config
|
||||
pass
|
||||
|
||||
if os.path.exists(os.path.join(".", 'config', "sensor.json")) is False:
|
||||
srcfile = os.path.join(os.path.dirname(__file__), "config", "sensor.json")
|
||||
destfile = os.path.join(".", 'config')
|
||||
shutil.copy(srcfile, destfile)
|
||||
def setup(self):
|
||||
print("Setting up CraftBeerPi")
|
||||
self.config.create_home_folder_structure()
|
||||
self.config.create_config_file()
|
||||
|
||||
if os.path.exists(os.path.join(".", 'config', "kettle.json")) is False:
|
||||
srcfile = os.path.join(os.path.dirname(__file__), "config", "kettle.json")
|
||||
destfile = os.path.join(".", 'config')
|
||||
shutil.copy(srcfile, destfile)
|
||||
def start(self):
|
||||
if self.config.check_for_setup() is False:
|
||||
return
|
||||
print("START")
|
||||
cbpi = CraftBeerPi(self.config)
|
||||
cbpi.start()
|
||||
|
||||
if os.path.exists(os.path.join(".", 'config', "fermenter_data.json")) is False:
|
||||
srcfile = os.path.join(os.path.dirname(__file__), "config", "fermenter_data.json")
|
||||
destfile = os.path.join(".", 'config')
|
||||
shutil.copy(srcfile, destfile)
|
||||
|
||||
if os.path.exists(os.path.join(".", 'config', "step_data.json")) is False:
|
||||
srcfile = os.path.join(os.path.dirname(__file__), "config", "step_data.json")
|
||||
destfile = os.path.join(".", 'config')
|
||||
shutil.copy(srcfile, destfile)
|
||||
|
||||
if os.path.exists(os.path.join(".", 'config', "config.json")) is False:
|
||||
srcfile = os.path.join(os.path.dirname(__file__), "config", "config.json")
|
||||
destfile = os.path.join(".", 'config')
|
||||
shutil.copy(srcfile, destfile)
|
||||
|
||||
if os.path.exists(os.path.join(".", 'config', "dashboard", "cbpi_dashboard_1.json")) is False:
|
||||
srcfile = os.path.join(os.path.dirname(__file__), "config", "dashboard", "cbpi_dashboard_1.json")
|
||||
destfile = os.path.join(".", "config", "dashboard")
|
||||
shutil.copy(srcfile, destfile)
|
||||
|
||||
if os.path.exists(os.path.join(".", 'config', "carftbeerpi.service")) is False:
|
||||
srcfile = os.path.join(os.path.dirname(__file__), "config", "craftbeerpi.service")
|
||||
destfile = os.path.join(".", 'config')
|
||||
shutil.copy(srcfile, destfile)
|
||||
|
||||
if os.path.exists(os.path.join(".", 'config', "chromium.desktop")) is False:
|
||||
srcfile = os.path.join(os.path.dirname(__file__), "config", "chromium.desktop")
|
||||
destfile = os.path.join(".", 'config')
|
||||
shutil.copy(srcfile, destfile)
|
||||
|
||||
print("Config Folder created")
|
||||
|
||||
|
||||
def create_home_folder_structure():
|
||||
pathlib.Path(os.path.join(".", 'logs/sensors')).mkdir(parents=True, exist_ok=True)
|
||||
pathlib.Path(os.path.join(".", 'config')).mkdir(parents=True, exist_ok=True)
|
||||
pathlib.Path(os.path.join(".", 'config/dashboard')).mkdir(parents=True, exist_ok=True)
|
||||
pathlib.Path(os.path.join(".", 'config/dashboard/widgets')).mkdir(parents=True, exist_ok=True)
|
||||
pathlib.Path(os.path.join(".", 'config/recipes')).mkdir(parents=True, exist_ok=True)
|
||||
pathlib.Path(os.path.join(".", 'config/fermenterrecipes')).mkdir(parents=True, exist_ok=True)
|
||||
pathlib.Path(os.path.join(".", 'config/upload')).mkdir(parents=True, exist_ok=True)
|
||||
print("Folder created")
|
||||
|
||||
|
||||
def setup_one_wire():
|
||||
def setup_one_wire(self):
|
||||
print("Setting up 1Wire")
|
||||
with open('/boot/config.txt', 'w') as f:
|
||||
f.write("dtoverlay=w1-gpio,gpiopin=4,pullup=on")
|
||||
print("/boot/config.txt created")
|
||||
|
||||
def list_one_wire():
|
||||
def list_one_wire(self):
|
||||
print("List 1Wire")
|
||||
call(["modprobe", "w1-gpio"])
|
||||
call(["modprobe", "w1-therm"])
|
||||
|
@ -108,88 +50,7 @@ def list_one_wire():
|
|||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
def copy_splash():
|
||||
srcfile = os.path.join(".", "config", "splash.png")
|
||||
destfile = os.path.join(".", 'config')
|
||||
shutil.copy(srcfile, destfile)
|
||||
print("Splash Srceen created")
|
||||
|
||||
|
||||
def clear_db():
|
||||
import os.path
|
||||
if os.path.exists(os.path.join(".", "craftbeerpi.db")) is True:
|
||||
os.remove(os.path.join(".", "craftbeerpi.db"))
|
||||
print("database cleared")
|
||||
|
||||
def recursive_chown(path, owner, group):
|
||||
for dirpath, dirnames, filenames in os.walk(path):
|
||||
shutil.chown(dirpath, owner, group)
|
||||
for filename in filenames:
|
||||
shutil.chown(os.path.join(dirpath, filename), owner, group)
|
||||
|
||||
def check_for_setup():
|
||||
if os.path.exists(os.path.join(".", "config", "config.yaml")) is False:
|
||||
print("***************************************************")
|
||||
print("CraftBeerPi Config File not found: %s" % os.path.join(".", "config", "config.yaml"))
|
||||
print("Please run 'cbpi setup' before starting the server ")
|
||||
print("***************************************************")
|
||||
return False
|
||||
if os.path.exists(os.path.join(".", "config", "upload")) is False:
|
||||
print("***************************************************")
|
||||
print("CraftBeerPi upload folder not found: %s" % os.path.join(".", "config/upload"))
|
||||
print("Please run 'cbpi setup' before starting the server ")
|
||||
print("***************************************************")
|
||||
return False
|
||||
# if os.path.exists(os.path.join(".", "config", "fermenterrecipes")) is False:
|
||||
# print("***************************************************")
|
||||
# print("CraftBeerPi fermenterrecipes folder not found: %s" % os.path.join(".", "config/fermenterrecipes"))
|
||||
# print("Please run 'cbpi setup' before starting the server ")
|
||||
# print("***************************************************")
|
||||
# return False
|
||||
backupfile = os.path.join(".", "restored_config.zip")
|
||||
if os.path.exists(os.path.join(backupfile)) is True:
|
||||
print("***************************************************")
|
||||
print("Found backup of config. Starting restore")
|
||||
required_content=['dashboard/', 'recipes/', 'upload/', 'config.json', 'config.yaml']
|
||||
zip=zipfile.ZipFile(backupfile)
|
||||
zip_content_list = zip.namelist()
|
||||
zip_content = True
|
||||
print("Checking content of zip file")
|
||||
for content in required_content:
|
||||
try:
|
||||
check = zip_content_list.index(content)
|
||||
except:
|
||||
zip_content = False
|
||||
|
||||
if zip_content == True:
|
||||
print("Found correct content. Starting Restore process")
|
||||
output_path = pathlib.Path(os.path.join(".", 'config'))
|
||||
system = platform.system()
|
||||
print(system)
|
||||
if system != "Windows":
|
||||
owner = output_path.owner()
|
||||
group = output_path.group()
|
||||
print("Removing old config folder")
|
||||
shutil.rmtree(output_path, ignore_errors=True)
|
||||
print("Extracting zip file to config folder")
|
||||
zip.extractall(output_path)
|
||||
zip.close()
|
||||
if system != "Windows":
|
||||
print(f"Changing owner and group of config folder recursively to {owner}:{group}")
|
||||
recursive_chown(output_path, owner, group)
|
||||
print("Removing backup file")
|
||||
os.remove(backupfile)
|
||||
else:
|
||||
print("Wrong Content in zip file. No restore possible")
|
||||
print("Removing zip file")
|
||||
os.remove(backupfile)
|
||||
print("***************************************************")
|
||||
|
||||
return True
|
||||
else:
|
||||
return True
|
||||
|
||||
def plugins_list():
|
||||
def plugins_list(self):
|
||||
result = []
|
||||
print("")
|
||||
print(Fore.LIGHTYELLOW_EX,"List of active plugins", Style.RESET_ALL)
|
||||
|
@ -201,7 +62,6 @@ def plugins_list():
|
|||
if name.startswith('cbpi') and len(name) > 4
|
||||
}
|
||||
for key, module in discovered_plugins.items():
|
||||
|
||||
try:
|
||||
meta = metadata(key)
|
||||
result.append(dict(Name=meta["Name"], Version=meta["Version"], Author=meta["Author"], Homepage=meta["Home-page"], Summary=meta["Summary"]))
|
||||
|
@ -211,9 +71,7 @@ def plugins_list():
|
|||
print(Fore.LIGHTGREEN_EX, tabulate(result, headers="keys"), Style.RESET_ALL)
|
||||
|
||||
|
||||
|
||||
def plugin_create():
|
||||
|
||||
def plugin_create(self):
|
||||
print("Plugin Creation")
|
||||
print("")
|
||||
|
||||
|
@ -271,7 +129,6 @@ def plugin_create():
|
|||
with open(os.path.join(".", name, name, "config.yaml"), "w") as fh:
|
||||
fh.write(outputText)
|
||||
|
||||
|
||||
print("")
|
||||
print("")
|
||||
print("Plugin {}{}{} created! ".format(Fore.LIGHTGREEN_EX, name, Style.RESET_ALL) )
|
||||
|
@ -282,66 +139,8 @@ def plugin_create():
|
|||
print("")
|
||||
print("")
|
||||
|
||||
|
||||
|
||||
@click.group()
|
||||
def main():
|
||||
print("---------------------")
|
||||
print("Welcome to CBPi")
|
||||
print("---------------------")
|
||||
level = logging.INFO
|
||||
logging.basicConfig(level=level, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
|
||||
pass
|
||||
|
||||
|
||||
@click.command()
|
||||
def setup():
|
||||
'''Create Config folder'''
|
||||
print("Setting up CraftBeerPi")
|
||||
create_home_folder_structure()
|
||||
create_config_file()
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option('--list', is_flag=True, help="List all 1Wire Devices")
|
||||
@click.option('--setup', is_flag=True, help="Setup 1Wire on Raspberry Pi")
|
||||
def onewire(list, setup):
|
||||
'''Setup 1wire on Raspberry Pi'''
|
||||
if setup is True:
|
||||
setup_one_wire()
|
||||
if list is True:
|
||||
list_one_wire()
|
||||
|
||||
|
||||
|
||||
@click.command()
|
||||
def start():
|
||||
'''Lets go brewing'''
|
||||
if check_for_setup() is False:
|
||||
return
|
||||
print("Starting up CraftBeerPi ...")
|
||||
cbpi = CraftBeerPi()
|
||||
cbpi.start()
|
||||
|
||||
|
||||
@click.command()
|
||||
def plugins():
|
||||
'''List active plugins'''
|
||||
plugins_list()
|
||||
return
|
||||
|
||||
|
||||
|
||||
|
||||
@click.command()
|
||||
def create():
|
||||
'''Create New Plugin'''
|
||||
plugin_create()
|
||||
|
||||
@click.command()
|
||||
@click.argument('name')
|
||||
def autostart(name):
|
||||
'''(on|off|status) Enable or disable autostart'''
|
||||
def autostart(self, name):
|
||||
'''Enable or disable autostart'''
|
||||
if(name == "status"):
|
||||
if os.path.exists(os.path.join("/etc/systemd/system","craftbeerpi.service")) is True:
|
||||
print("CraftBeerPi Autostart is {}ON{}".format(Fore.LIGHTGREEN_EX,Style.RESET_ALL))
|
||||
|
@ -351,7 +150,7 @@ def autostart(name):
|
|||
print("Add craftbeerpi.service to systemd")
|
||||
try:
|
||||
if os.path.exists(os.path.join("/etc/systemd/system","craftbeerpi.service")) is False:
|
||||
srcfile = os.path.join(".", "config", "craftbeerpi.service")
|
||||
srcfile = self.config.get_file_path("craftbeerpi.service")
|
||||
destfile = os.path.join("/etc/systemd/system")
|
||||
shutil.copy(srcfile, destfile)
|
||||
print("Copied craftbeerpi.service to /etc/systemd/system")
|
||||
|
@ -388,12 +187,8 @@ def autostart(name):
|
|||
return
|
||||
|
||||
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.argument('name')
|
||||
def chromium(name):
|
||||
'''(on|off|status) Enable or disable Kiosk mode'''
|
||||
def chromium(self, name):
|
||||
'''Enable or disable autostart'''
|
||||
if(name == "status"):
|
||||
if os.path.exists(os.path.join("/etc/xdg/autostart/","chromium.desktop")) is True:
|
||||
print("CraftBeerPi Chromium Desktop is {}ON{}".format(Fore.LIGHTGREEN_EX,Style.RESET_ALL))
|
||||
|
@ -403,7 +198,7 @@ def chromium(name):
|
|||
print("Add chromium.desktop to /etc/xdg/autostart/")
|
||||
try:
|
||||
if os.path.exists(os.path.join("/etc/xdg/autostart/","chromium.desktop")) is False:
|
||||
srcfile = os.path.join(".", "config", "chromium.desktop")
|
||||
srcfile = self.config.get_file_path("chromium.desktop")
|
||||
destfile = os.path.join("/etc/xdg/autostart/")
|
||||
shutil.copy(srcfile, destfile)
|
||||
print("Copied chromium.desktop to /etc/xdg/autostart/")
|
||||
|
@ -426,10 +221,65 @@ def chromium(name):
|
|||
return
|
||||
return
|
||||
|
||||
main.add_command(setup)
|
||||
main.add_command(start)
|
||||
main.add_command(autostart)
|
||||
main.add_command(chromium)
|
||||
main.add_command(plugins)
|
||||
main.add_command(onewire)
|
||||
main.add_command(create)
|
||||
|
||||
@click.group()
|
||||
@click.pass_context
|
||||
@click.option('--config-folder-path', '-c', default="./config", type=click.Path(), help="Specify where the config folder is located. Defaults to './config'.")
|
||||
def main(context, config_folder_path):
|
||||
print("---------------------")
|
||||
print("Welcome to CBPi")
|
||||
print("---------------------")
|
||||
level = logging.INFO
|
||||
logging.basicConfig(level=level, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
|
||||
cbpi_cli = CraftBeerPiCli(ConfigFolder(config_folder_path))
|
||||
context.obj = cbpi_cli
|
||||
pass
|
||||
|
||||
@main.command()
|
||||
@click.pass_context
|
||||
def setup(context):
|
||||
'''Create Config folder'''
|
||||
context.obj.setup()
|
||||
|
||||
@main.command()
|
||||
@click.pass_context
|
||||
@click.option('--list', is_flag=True, help="List all 1Wire Devices")
|
||||
@click.option('--setup', is_flag=True, help="Setup 1Wire on Raspberry Pi")
|
||||
def onewire(context, list, setup):
|
||||
'''Setup 1wire on Raspberry Pi'''
|
||||
if setup is True:
|
||||
context.obj.setup_one_wire()
|
||||
if list is True:
|
||||
context.obj.list_one_wire()
|
||||
|
||||
@main.command()
|
||||
@click.pass_context
|
||||
def start(context):
|
||||
context.obj.start()
|
||||
|
||||
@main.command()
|
||||
@click.pass_context
|
||||
def plugins(context):
|
||||
'''List active plugins'''
|
||||
context.obj.plugins_list()
|
||||
|
||||
@click.command()
|
||||
@click.pass_context
|
||||
def create(context):
|
||||
'''Create New Plugin'''
|
||||
context.obj.plugin_create()
|
||||
|
||||
@main.command()
|
||||
@click.pass_context
|
||||
@click.argument('name')
|
||||
def autostart(context, name):
|
||||
'''(on|off|status) Enable or disable autostart'''
|
||||
context.obj.autostart(name)
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.pass_context
|
||||
@click.argument('name')
|
||||
def chromium(context, name):
|
||||
'''(on|off|status) Enable or disable Kiosk mode'''
|
||||
context.obj.chromium(name)
|
||||
|
|
130
cbpi/configFolder.py
Normal file
130
cbpi/configFolder.py
Normal file
|
@ -0,0 +1,130 @@
|
|||
import os
|
||||
import pathlib
|
||||
import platform
|
||||
import shutil
|
||||
import zipfile
|
||||
import glob
|
||||
|
||||
|
||||
class ConfigFolder:
|
||||
def __init__(self, configFolderPath):
|
||||
self._rawPath = configFolderPath
|
||||
|
||||
def config_file_exists(self, path):
|
||||
return os.path.exists(self.get_file_path(path))
|
||||
|
||||
def get_file_path(self, file):
|
||||
return os.path.join(self._rawPath, file)
|
||||
|
||||
def get_upload_file(self, file):
|
||||
return os.path.join(self._rawPath, 'upload', file)
|
||||
|
||||
def get_recipe_file_by_id(self, recipe_id):
|
||||
return os.path.join(self._rawPath, 'recipes', "{}.yaml".format(recipe_id))
|
||||
|
||||
def get_fermenter_recipe_by_id(self, recipe_id):
|
||||
return os.path.join(self._rawPath, 'fermenterrecipes', "{}.yaml".format(recipe_id))
|
||||
|
||||
def get_all_fermenter_recipes(self):
|
||||
fermenter_recipes_folder = os.path.join(self._rawPath, 'fermenterrecipes', '*.yaml')
|
||||
return glob.glob(fermenter_recipes_folder)
|
||||
|
||||
def check_for_setup(self):
|
||||
if self.config_file_exists("config.yaml") is False:
|
||||
print("***************************************************")
|
||||
print("CraftBeerPi Config File not found: %s" % self.get_file_path("config.yaml"))
|
||||
print("Please run 'cbpi setup' before starting the server ")
|
||||
print("***************************************************")
|
||||
return False
|
||||
if self.config_file_exists("upload") is False:
|
||||
print("***************************************************")
|
||||
print("CraftBeerPi upload folder not found: %s" % self.get_file_path("upload"))
|
||||
print("Please run 'cbpi setup' before starting the server ")
|
||||
print("***************************************************")
|
||||
return False
|
||||
# if os.path.exists(os.path.join(".", "config", "fermenterrecipes")) is False:
|
||||
# print("***************************************************")
|
||||
# print("CraftBeerPi fermenterrecipes folder not found: %s" % os.path.join(".", "config/fermenterrecipes"))
|
||||
# print("Please run 'cbpi setup' before starting the server ")
|
||||
# print("***************************************************")
|
||||
# return False
|
||||
backupfile = os.path.join(".", "restored_config.zip")
|
||||
if os.path.exists(os.path.join(backupfile)) is True:
|
||||
print("***************************************************")
|
||||
print("Found backup of config. Starting restore")
|
||||
required_content=['dashboard/', 'recipes/', 'upload/', 'config.json', 'config.yaml']
|
||||
zip=zipfile.ZipFile(backupfile)
|
||||
zip_content_list = zip.namelist()
|
||||
zip_content = True
|
||||
print("Checking content of zip file")
|
||||
for content in required_content:
|
||||
try:
|
||||
check = zip_content_list.index(content)
|
||||
except:
|
||||
zip_content = False
|
||||
|
||||
if zip_content == True:
|
||||
print("Found correct content. Starting Restore process")
|
||||
output_path = pathlib.Path(self._rawPath)
|
||||
system = platform.system()
|
||||
print(system)
|
||||
if system != "Windows":
|
||||
owner = output_path.owner()
|
||||
group = output_path.group()
|
||||
print("Removing old config folder")
|
||||
shutil.rmtree(output_path, ignore_errors=True)
|
||||
print("Extracting zip file to config folder")
|
||||
zip.extractall(output_path)
|
||||
zip.close()
|
||||
if system != "Windows":
|
||||
print(f"Changing owner and group of config folder recursively to {owner}:{group}")
|
||||
self.recursive_chown(output_path, owner, group)
|
||||
print("Removing backup file")
|
||||
os.remove(backupfile)
|
||||
else:
|
||||
print("Wrong Content in zip file. No restore possible")
|
||||
print("Removing zip file")
|
||||
os.remove(backupfile)
|
||||
print("***************************************************")
|
||||
|
||||
def copyDefaultFileIfNotExists(self, file):
|
||||
if self.config_file_exists(file) is False:
|
||||
srcfile = os.path.join(os.path.dirname(__file__), "config", file)
|
||||
destfile = os.path.join(self._rawPath, file)
|
||||
shutil.copy(srcfile, destfile)
|
||||
|
||||
def create_config_file(self):
|
||||
self.copyDefaultFileIfNotExists("config.yaml")
|
||||
self.copyDefaultFileIfNotExists("actor.json")
|
||||
self.copyDefaultFileIfNotExists("sensor.json")
|
||||
self.copyDefaultFileIfNotExists("kettle.json")
|
||||
self.copyDefaultFileIfNotExists("fermenter_data.json")
|
||||
self.copyDefaultFileIfNotExists("step_data.json")
|
||||
self.copyDefaultFileIfNotExists("config.json")
|
||||
self.copyDefaultFileIfNotExists("craftbeerpi.service")
|
||||
self.copyDefaultFileIfNotExists("chromium.desktop")
|
||||
|
||||
if os.path.exists(os.path.join(self._rawPath, "dashboard", "cbpi_dashboard_1.json")) is False:
|
||||
srcfile = os.path.join(os.path.dirname(__file__), "config", "dashboard", "cbpi_dashboard_1.json")
|
||||
destfile = os.path.join(self._rawPath, "dashboard")
|
||||
shutil.copy(srcfile, destfile)
|
||||
|
||||
print("Config Folder created")
|
||||
|
||||
def create_home_folder_structure(configFolder):
|
||||
pathlib.Path(os.path.join(".", 'logs/sensors')).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
configFolder.create_folders()
|
||||
print("Folder created")
|
||||
|
||||
def create_folders(self):
|
||||
pathlib.Path(self._rawPath).mkdir(parents=True, exist_ok=True)
|
||||
pathlib.Path(os.path.join(self._rawPath, 'dashboard', 'widgets')).mkdir(parents=True, exist_ok=True)
|
||||
pathlib.Path(os.path.join(self._rawPath, 'recipes')).mkdir(parents=True, exist_ok=True)
|
||||
pathlib.Path(os.path.join(self._rawPath, 'upload')).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def recursive_chown(path, owner, group):
|
||||
for dirpath, dirnames, filenames in os.walk(path):
|
||||
shutil.chown(dirpath, owner, group)
|
||||
for filename in filenames:
|
||||
shutil.chown(os.path.join(dirpath, filename), owner, group)
|
|
@ -23,7 +23,7 @@ class BasicController:
|
|||
self.data = []
|
||||
self.autostart = True
|
||||
#self._loop = asyncio.get_event_loop()
|
||||
self.path = os.path.join(".", 'config', file)
|
||||
self.path = self.cbpi.config_folder.get_file_path(file)
|
||||
self.cbpi.app.on_cleanup.append(self.shutdown)
|
||||
|
||||
async def init(self):
|
||||
|
|
|
@ -14,8 +14,8 @@ class ConfigController:
|
|||
self.logger = logging.getLogger(__name__)
|
||||
self.cbpi = cbpi
|
||||
self.cbpi.register(self)
|
||||
self.path = os.path.join(".", 'config', "config.json")
|
||||
self.path_static = os.path.join(".", 'config', "config.yaml")
|
||||
self.path = cbpi.config_folder.get_file_path("config.json")
|
||||
self.path_static = cbpi.config_folder.get_file_path("config.yaml")
|
||||
|
||||
def get_state(self):
|
||||
|
||||
|
|
|
@ -18,14 +18,14 @@ class DashboardController:
|
|||
self.logger = logging.getLogger(__name__)
|
||||
self.cbpi.register(self)
|
||||
|
||||
self.path = os.path.join(".", 'config', "cbpi_dashboard_1.json")
|
||||
self.path = cbpi.config_folder.get_file_path("cbpi_dashboard_1.json")
|
||||
|
||||
async def init(self):
|
||||
pass
|
||||
|
||||
async def get_content(self, dashboard_id):
|
||||
try:
|
||||
self.path = os.path.join(".", 'config', "cbpi_dashboard_"+ str(dashboard_id) +".json")
|
||||
self.path = self.cbpi.config_folder.get_file_path("cbpi_dashboard_"+ str(dashboard_id) +".json")
|
||||
logging.info(self.path)
|
||||
with open(self.path) as json_file:
|
||||
data = json.load(json_file)
|
||||
|
@ -35,21 +35,21 @@ class DashboardController:
|
|||
|
||||
async def add_content(self, dashboard_id, data):
|
||||
print(data)
|
||||
self.path = os.path.join(".", 'config', "cbpi_dashboard_" + str(dashboard_id)+ ".json")
|
||||
self.path = self.cbpi.config_folder.get_file_path("cbpi_dashboard_" + str(dashboard_id)+ ".json")
|
||||
with open(self.path, 'w') as outfile:
|
||||
json.dump(data, outfile, indent=4, sort_keys=True)
|
||||
self.cbpi.notify(title="Dashboard {}".format(dashboard_id), message="Saved Successfully", type=NotificationType.SUCCESS)
|
||||
return {"status": "OK"}
|
||||
|
||||
async def delete_content(self, dashboard_id):
|
||||
self.path = os.path.join(".", 'config', "cbpi_dashboard_"+ str(dashboard_id)+ ".json")
|
||||
self.path = self.cbpi.config_folder.get_file_path("cbpi_dashboard_"+ str(dashboard_id)+ ".json")
|
||||
if os.path.exists(self.path):
|
||||
os.remove(self.path)
|
||||
self.cbpi.notify(title="Dashboard {}".format(dashboard_id), message="Deleted Successfully", type=NotificationType.SUCCESS)
|
||||
|
||||
|
||||
async def get_custom_widgets(self):
|
||||
path = os.path.join(".", 'config', "dashboard", "widgets")
|
||||
path = os.path.join(self.cbpi.config_folder.get_file_path("dashboard"), "widgets")
|
||||
onlyfiles = [os.path.splitext(f)[0] for f in sorted(listdir(path)) if isfile(join(path, f)) and f.endswith(".svg")]
|
||||
return onlyfiles
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ class FermentationController:
|
|||
self.update_key = "fermenterupdate"
|
||||
self.cbpi = cbpi
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self.path = os.path.join(".", 'config', "fermenter_data.json")
|
||||
self.path = self.cbpi.config_folder.get_file_path("fermenter_data.json")
|
||||
self.data = []
|
||||
self.types = {}
|
||||
self.steptypes = {}
|
||||
|
@ -35,16 +35,16 @@ class FermentationController:
|
|||
pass
|
||||
|
||||
def check_fermenter_file(self):
|
||||
if os.path.exists(os.path.join(".", 'config', "fermenter_data.json")) is False:
|
||||
if os.path.exists(self.cbpi.config_folder.get_file_path("fermenter_data.json")) is False:
|
||||
logging.info("INIT fermenter_data.json file")
|
||||
data = {
|
||||
"data": [
|
||||
]
|
||||
}
|
||||
destfile = os.path.join(".", 'config', "fermenter_data.json")
|
||||
destfile = self.cbpi.config_folder.get_file_path("fermenter_data.json")
|
||||
json.dump(data,open(destfile,'w'),indent=4, sort_keys=True)
|
||||
|
||||
pathlib.Path(os.path.join(".", 'config/fermenterrecipes')).mkdir(parents=True, exist_ok=True)
|
||||
pathlib.Path(self.cbpi.config_folder.get_file_path("fermenterrecipes")).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
async def shutdown(self, app=None, fermenterid=None):
|
||||
self.save()
|
||||
|
@ -525,7 +525,7 @@ class FermentationController:
|
|||
|
||||
async def savetobook(self, fermenterid):
|
||||
name = shortuuid.uuid()
|
||||
path = os.path.join(".", 'config', "fermenterrecipes", "{}.yaml".format(name))
|
||||
path = self.cbpi.config_folder.get_fermenter_recipe_by_id(name)
|
||||
fermenter=self._find_by_id(fermenterid)
|
||||
try:
|
||||
brewname = fermenter.brewname
|
||||
|
|
|
@ -29,27 +29,25 @@ class FermenterRecipeController:
|
|||
|
||||
async def create(self, name):
|
||||
id = shortuuid.uuid()
|
||||
path = os.path.join(".", 'config', "fermenterrecipes", "{}.yaml".format(id))
|
||||
path = self.cbpi.config_folder.get_fermenter_recipe_by_id(id)
|
||||
data = dict(basic=dict(name=name), steps=[])
|
||||
with open(path, "w") as file:
|
||||
yaml.dump(data, file)
|
||||
return id
|
||||
|
||||
async def save(self, name, data):
|
||||
path = os.path.join(".", 'config', "fermenterrecipes", "{}.yaml".format(name))
|
||||
path = self.cbpi.config_folder.get_fermenter_recipe_by_id(name)
|
||||
logging.info(data)
|
||||
with open(path, "w") as file:
|
||||
yaml.dump(data, file, indent=4, sort_keys=True)
|
||||
|
||||
|
||||
async def get_recipes(self):
|
||||
path = os.path.join(".", 'config', "fermenterrecipes")
|
||||
onlyfiles = [os.path.splitext(f)[0] for f in listdir(path) if isfile(join(path, f)) and f.endswith(".yaml")]
|
||||
fermenter_recipes = self.cbpi.config_folder.get_all_fermenter_recipes()
|
||||
|
||||
result = []
|
||||
for filename in onlyfiles:
|
||||
recipe_path = os.path.join(".", 'config', "fermenterrecipes", "%s.yaml" % filename)
|
||||
with open(recipe_path) as file:
|
||||
for filename in fermenter_recipes:
|
||||
with open(filename) as file:
|
||||
data = yaml.load(file, Loader=yaml.FullLoader)
|
||||
dataset = data["basic"]
|
||||
dataset["file"] = filename
|
||||
|
@ -58,27 +56,24 @@ class FermenterRecipeController:
|
|||
return result
|
||||
|
||||
async def get_by_name(self, name):
|
||||
|
||||
recipe_path = os.path.join(".", 'config', "fermenterrecipes", "%s.yaml" % name)
|
||||
recipe_path = self.cbpi.config_folder.get_fermenter_recipe_by_id(name)
|
||||
with open(recipe_path) as file:
|
||||
return yaml.load(file, Loader=yaml.FullLoader)
|
||||
|
||||
|
||||
async def remove(self, name):
|
||||
path = os.path.join(".", 'config', "fermenterrecipes", "{}.yaml".format(name))
|
||||
path = self.cbpi.config_folder.get_fermenter_recipe_by_id(name)
|
||||
os.remove(path)
|
||||
|
||||
|
||||
async def brew(self, recipeid, fermenterid, name):
|
||||
recipe_path = self.cbpi.config_folder.get_fermenter_recipe_by_id(recipeid)
|
||||
|
||||
recipe_path = os.path.join(".", 'config', "fermenterrecipes", "%s.yaml" % recipeid)
|
||||
logging.info(recipe_path)
|
||||
with open(recipe_path) as file:
|
||||
data = yaml.load(file, Loader=yaml.FullLoader)
|
||||
await self.cbpi.fermenter.load_recipe(data, fermenterid, name)
|
||||
|
||||
async def clone(self, id, new_name):
|
||||
recipe_path = os.path.join(".", 'config', "fermenterrecipes", "%s.yaml" % id)
|
||||
recipe_path = self.cbpi.config_folder.get_fermenter_recipe_by_id(id)
|
||||
with open(recipe_path) as file:
|
||||
data = yaml.load(file, Loader=yaml.FullLoader)
|
||||
data["basic"]["name"] = new_name
|
||||
|
|
|
@ -29,26 +29,26 @@ class RecipeController:
|
|||
|
||||
async def create(self, name):
|
||||
id = shortuuid.uuid()
|
||||
path = os.path.join(".", 'config', "recipes", "{}.yaml".format(id))
|
||||
path = self.cbpi.config_folder.get_recipe_file_by_id(id)
|
||||
data = dict(basic=dict(name=name, author=self.cbpi.config.get("AUTHOR", "John Doe")), steps=[])
|
||||
with open(path, "w") as file:
|
||||
yaml.dump(data, file)
|
||||
return id
|
||||
|
||||
async def save(self, name, data):
|
||||
path = os.path.join(".", 'config', "recipes", "{}.yaml".format(name))
|
||||
path = self.cbpi.config_folder.get_recipe_file_by_id(name)
|
||||
logging.info(data)
|
||||
with open(path, "w") as file:
|
||||
yaml.dump(data, file, indent=4, sort_keys=True)
|
||||
|
||||
|
||||
async def get_recipes(self):
|
||||
path = os.path.join(".", 'config', "recipes")
|
||||
path = self.cbpi.config_folder.get_file_path("recipes")
|
||||
onlyfiles = [os.path.splitext(f)[0] for f in listdir(path) if isfile(join(path, f)) and f.endswith(".yaml")]
|
||||
|
||||
result = []
|
||||
for filename in onlyfiles:
|
||||
recipe_path = os.path.join(".", 'config', "recipes", "%s.yaml" % filename)
|
||||
recipe_path = self.cbpi.config_folder.get_recipe_file_by_id(filename)
|
||||
with open(recipe_path) as file:
|
||||
data = yaml.load(file, Loader=yaml.FullLoader)
|
||||
dataset = data["basic"]
|
||||
|
@ -58,25 +58,25 @@ class RecipeController:
|
|||
|
||||
async def get_by_name(self, name):
|
||||
|
||||
recipe_path = os.path.join(".", 'config', "recipes", "%s.yaml" % name)
|
||||
recipe_path = self.cbpi.config_folder.get_recipe_file_by_id(name)
|
||||
with open(recipe_path) as file:
|
||||
return yaml.load(file, Loader=yaml.FullLoader)
|
||||
|
||||
|
||||
async def remove(self, name):
|
||||
path = os.path.join(".", 'config', "recipes", "{}.yaml".format(name))
|
||||
path = self.cbpi.config_folder.get_recipe_file_by_id(name)
|
||||
os.remove(path)
|
||||
|
||||
|
||||
async def brew(self, name):
|
||||
|
||||
recipe_path = os.path.join(".", 'config', "recipes", "%s.yaml" % name)
|
||||
recipe_path = self.cbpi.config_folder.get_recipe_file_by_id(name)
|
||||
with open(recipe_path) as file:
|
||||
data = yaml.load(file, Loader=yaml.FullLoader)
|
||||
await self.cbpi.step.load_recipe(data)
|
||||
|
||||
async def clone(self, id, new_name):
|
||||
recipe_path = os.path.join(".", 'config', "recipes", "%s.yaml" % id)
|
||||
recipe_path = self.cbpi.config_folder.get_recipe_file_by_id(id)
|
||||
with open(recipe_path) as file:
|
||||
data = yaml.load(file, Loader=yaml.FullLoader)
|
||||
data["basic"]["name"] = new_name
|
||||
|
|
|
@ -19,7 +19,7 @@ class StepController:
|
|||
def __init__(self, cbpi):
|
||||
self.cbpi = cbpi
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self.path = os.path.join(".", 'config', "step_data.json")
|
||||
self.path = self.cbpi.config_folder.get_file_path("step_data.json")
|
||||
#self._loop = asyncio.get_event_loop()
|
||||
self.basic_data = {}
|
||||
self.step = None
|
||||
|
@ -324,7 +324,7 @@ class StepController:
|
|||
|
||||
async def savetobook(self):
|
||||
name = shortuuid.uuid()
|
||||
path = os.path.join(".", 'config', "recipes", "{}.yaml".format(name))
|
||||
path = os.path.join(self.cbpi.config_folder.get_file_path("recipes"), "{}.yaml".format(name))
|
||||
data = dict(basic=self.basic_data, steps=list(map(lambda item: item.to_dict(), self.profile)))
|
||||
with open(path, "w") as file:
|
||||
yaml.dump(data, file)
|
||||
|
|
|
@ -38,7 +38,7 @@ class SystemController:
|
|||
|
||||
async def backupConfig(self):
|
||||
output_filename = "cbpi4_config"
|
||||
dir_name = pathlib.Path(os.path.join(".", 'config'))
|
||||
dir_name = pathlib.Path(self.cbpi.config_folder.get_file_path(''))
|
||||
shutil.make_archive(output_filename, 'zip', dir_name)
|
||||
|
||||
async def downloadlog(self, logtime):
|
||||
|
@ -153,7 +153,7 @@ class SystemController:
|
|||
try:
|
||||
content = svg_file.read().decode('utf-8','replace')
|
||||
if svg_file and self.allowed_file(filename, 'svg'):
|
||||
self.path = os.path.join(".","config","dashboard","widgets", filename)
|
||||
self.path = os.path.join(self.cbpi.config_folder.get_file_path("dashboard"),"widgets", filename)
|
||||
logging.info(self.path)
|
||||
|
||||
f=open(self.path, "w")
|
||||
|
|
|
@ -32,7 +32,7 @@ class UploadController:
|
|||
|
||||
async def get_kbh_recipes(self):
|
||||
try:
|
||||
path = os.path.join(".", 'config', "upload", "kbh.db")
|
||||
path = self.cbpi.config_folder.get_upload_file("kbh.db")
|
||||
conn = sqlite3.connect(path)
|
||||
c = conn.cursor()
|
||||
c.execute('SELECT ID, Sudname, Status FROM Sud')
|
||||
|
@ -47,7 +47,7 @@ class UploadController:
|
|||
|
||||
async def get_xml_recipes(self):
|
||||
try:
|
||||
path = os.path.join(".", 'config', "upload", "beer.xml")
|
||||
path = self.cbpi.config_folder.get_upload_file("beer.xml")
|
||||
e = xml.etree.ElementTree.parse(path).getroot()
|
||||
result =[]
|
||||
counter = 1
|
||||
|
@ -61,7 +61,7 @@ class UploadController:
|
|||
|
||||
async def get_json_recipes(self):
|
||||
try:
|
||||
path = os.path.join(".", 'config', "upload", "mmum.json")
|
||||
path = self.cbpi.config_folder.get_upload_file("mmum.json")
|
||||
e = json.load(open(path))
|
||||
result =[]
|
||||
result.append({'value': str(1), 'label': e['Name']})
|
||||
|
@ -123,7 +123,7 @@ class UploadController:
|
|||
try:
|
||||
beer_xml = recipe_file.read().decode('utf-8','replace')
|
||||
if recipe_file and self.allowed_file(filename, 'xml'):
|
||||
self.path = os.path.join(".", 'config', "upload", "beer.xml")
|
||||
self.path = self.cbpi.config_folder.get_upload_file("beer.xml")
|
||||
|
||||
f = open(self.path, "w")
|
||||
f.write(beer_xml)
|
||||
|
@ -137,7 +137,7 @@ class UploadController:
|
|||
try:
|
||||
mmum_json = recipe_file.read().decode('utf-8','replace')
|
||||
if recipe_file and self.allowed_file(filename, 'json'):
|
||||
self.path = os.path.join(".", 'config', "upload", "mmum.json")
|
||||
self.path = self.cbpi.config_folder.get_upload_file("mmum.json")
|
||||
|
||||
f = open(self.path, "w")
|
||||
f.write(mmum_json)
|
||||
|
@ -151,7 +151,7 @@ class UploadController:
|
|||
try:
|
||||
content = recipe_file.read()
|
||||
if recipe_file and self.allowed_file(filename, 'sqlite'):
|
||||
self.path = os.path.join(".", 'config', "upload", "kbh.db")
|
||||
self.path = self.cbpi.config_folder.get_upload_file("kbh.db")
|
||||
|
||||
f=open(self.path, "wb")
|
||||
f.write(content)
|
||||
|
@ -168,7 +168,7 @@ class UploadController:
|
|||
config = self.get_config_values()
|
||||
if self.kettle is not None:
|
||||
# load beerxml file located in upload folder
|
||||
self.path = os.path.join(".", 'config', "upload", "kbh.db")
|
||||
self.path = self.cbpi.config_folder.get_upload_file("kbh.db")
|
||||
if os.path.exists(self.path) is False:
|
||||
self.cbpi.notify("File Not Found", "Please upload a kbh V2 databsel file", NotificationType.ERROR)
|
||||
|
||||
|
@ -318,7 +318,7 @@ class UploadController:
|
|||
self.cbpi.notify('Recipe Upload', 'No default Kettle defined. Please specify default Kettle in settings', NotificationType.ERROR)
|
||||
|
||||
def findMax(self, string):
|
||||
self.path = os.path.join(".", 'config', "upload", "mmum.json")
|
||||
self.path = self.cbpi.config_folder.get_upload_file("mmum.json")
|
||||
e = json.load(open(self.path))
|
||||
for idx in range(1,20):
|
||||
search_string = string.replace("%%",str(idx))
|
||||
|
@ -328,7 +328,7 @@ class UploadController:
|
|||
return i
|
||||
|
||||
def getJsonMashin(self, id):
|
||||
self.path = os.path.join(".", 'config', "upload", "mmum.json")
|
||||
self.path = self.cbpi.config_folder.get_upload_file("mmum.json")
|
||||
e = json.load(open(self.path))
|
||||
return float(e['Infusion_Einmaischtemperatur'])
|
||||
|
||||
|
@ -337,7 +337,7 @@ class UploadController:
|
|||
|
||||
if self.kettle is not None:
|
||||
# load mmum-json file located in upload folder
|
||||
self.path = os.path.join(".", 'config', "upload", "mmum.json")
|
||||
self.path = self.cbpi.config_folder.get_upload_file("mmum.json")
|
||||
if os.path.exists(self.path) is False:
|
||||
self.cbpi.notify("File Not Found", "Please upload a MMuM-JSON File", NotificationType.ERROR)
|
||||
|
||||
|
@ -551,7 +551,7 @@ class UploadController:
|
|||
|
||||
if self.kettle is not None:
|
||||
# load beerxml file located in upload folder
|
||||
self.path = os.path.join(".", 'config', "upload", "beer.xml")
|
||||
self.path = self.cbpi.config_folder.get_upload_file("beer.xml")
|
||||
if os.path.exists(self.path) is False:
|
||||
self.cbpi.notify("File Not Found", "Please upload a Beer.xml File", NotificationType.ERROR)
|
||||
|
||||
|
@ -692,7 +692,7 @@ class UploadController:
|
|||
temp = round(9.0 / 5.0 * float(e.find("STEP_TEMP").text) + 32, 2)
|
||||
steps.append({"name": e.find("NAME").text, "temp": temp, "timer": float(e.find("STEP_TIME").text)})
|
||||
elif recipe_type == "json":
|
||||
self.path = os.path.join(".", 'config', "upload", "mmum.json")
|
||||
self.path = self.cbpi.config_folder.get_upload_file("mmum.json")
|
||||
e = json.load(open(self.path))
|
||||
for idx in range(1,self.findMax("Infusion_Rastzeit%%")):
|
||||
if self.cbpi.config.get("TEMP_UNIT", "C") == "C":
|
||||
|
|
|
@ -86,7 +86,7 @@ async def error_middleware(request, handler):
|
|||
|
||||
class CraftBeerPi:
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, configFolder):
|
||||
|
||||
operationsystem= sys.platform
|
||||
if operationsystem.startswith('win'):
|
||||
|
@ -97,7 +97,8 @@ class CraftBeerPi:
|
|||
self.version = __version__
|
||||
self.codename = __codename__
|
||||
|
||||
self.static_config = load_config(os.path.join(".", 'config', "config.yaml"))
|
||||
self.config_folder = configFolder
|
||||
self.static_config = load_config(configFolder.get_file_path("config.yaml"))
|
||||
|
||||
logger.info("Init CraftBeerPI")
|
||||
|
||||
|
|
|
@ -24,6 +24,32 @@ class ActorHttpEndpoints():
|
|||
"""
|
||||
return web.json_response(data=self.controller.get_state())
|
||||
|
||||
@request_mapping(path="/{id:\w+}", auth_required=False)
|
||||
async def http_get_one(self, request):
|
||||
"""
|
||||
---
|
||||
description: Get one Actor
|
||||
tags:
|
||||
- Actor
|
||||
parameters:
|
||||
- name: "id"
|
||||
in: "path"
|
||||
description: "Actor ID"
|
||||
required: true
|
||||
type: "integer"
|
||||
format: "int64"
|
||||
responses:
|
||||
"200":
|
||||
description: successful operation
|
||||
"404":
|
||||
description: Actor not found
|
||||
"""
|
||||
actor = self.controller.find_by_id(request.match_info['id'])
|
||||
if (actor is None):
|
||||
return web.json_response(status=404)
|
||||
|
||||
return web.json_response(data=actor.to_dict(), status=200)
|
||||
|
||||
|
||||
@request_mapping(path="/", method="POST", auth_required=False)
|
||||
async def http_add(self, request):
|
||||
|
|
|
@ -12,7 +12,7 @@ class DashBoardHttpEndpoints:
|
|||
def __init__(self, cbpi):
|
||||
self.cbpi = cbpi
|
||||
self.controller = cbpi.dashboard
|
||||
self.cbpi.register(self, "/dashboard", os.path.join(".","config", "dashboard", "widgets"))
|
||||
self.cbpi.register(self, "/dashboard", os.path.join(cbpi.config_folder.get_file_path("dashboard"), "widgets"))
|
||||
|
||||
|
||||
@request_mapping(path="/{id:\d+}/content", auth_required=False)
|
||||
|
|
BIN
craftbeerpi.db
BIN
craftbeerpi.db
Binary file not shown.
|
@ -22,3 +22,5 @@ psutil==5.9.0
|
|||
zipp>=0.5
|
||||
PyInquirer==1.0.3
|
||||
colorama==0.4.4
|
||||
pytest-aiohttp
|
||||
coverage==6.3.1
|
||||
|
|
2
run.py
2
run.py
|
@ -1,3 +1,3 @@
|
|||
from cbpi.cli import main
|
||||
|
||||
main()
|
||||
main(auto_envvar_prefix='CBPI')
|
12
tests/cbpi-test-config/actor.json
Normal file
12
tests/cbpi-test-config/actor.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"data": [
|
||||
{
|
||||
"id": "3CUJte4bkxDMFCtLX8eqsX",
|
||||
"name": "SomeActor",
|
||||
"power": 100,
|
||||
"props": {},
|
||||
"state": false,
|
||||
"type": "DummyActor"
|
||||
}
|
||||
]
|
||||
}
|
6
tests/cbpi-test-config/chromium.desktop
Normal file
6
tests/cbpi-test-config/chromium.desktop
Normal file
|
@ -0,0 +1,6 @@
|
|||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Chromium
|
||||
Comment=Chromium Webbrowser
|
||||
NoDisplay=false
|
||||
Exec=chromium-browser --noerrordialogs --disable-session-crashed-bubble --disable-infobars --force-device-scale-factor=1.00 --start-fullscreen "http://localhost:8000"
|
148
tests/cbpi-test-config/config.json
Normal file
148
tests/cbpi-test-config/config.json
Normal file
|
@ -0,0 +1,148 @@
|
|||
{
|
||||
"AUTHOR": {
|
||||
"description": "Author",
|
||||
"name": "AUTHOR",
|
||||
"options": null,
|
||||
"type": "string",
|
||||
"value": "John Doe"
|
||||
},
|
||||
"BREWERY_NAME": {
|
||||
"description": "Brewery Name",
|
||||
"name": "BREWERY_NAME",
|
||||
"options": null,
|
||||
"type": "string",
|
||||
"value": "CraftBeerPi Brewery"
|
||||
},
|
||||
"MASH_TUN": {
|
||||
"description": "Default Mash Tun",
|
||||
"name": "MASH_TUN",
|
||||
"options": null,
|
||||
"type": "kettle",
|
||||
"value": ""
|
||||
},
|
||||
"AddMashInStep": {
|
||||
"description": "Add MashIn Step automatically if not defined in recipe",
|
||||
"name": "AddMashInStep",
|
||||
"options": [
|
||||
{
|
||||
"label": "Yes",
|
||||
"value": "Yes"
|
||||
},
|
||||
{
|
||||
"label": "No",
|
||||
"value": "No"
|
||||
}
|
||||
],
|
||||
"type": "select",
|
||||
"value": "Yes"
|
||||
},
|
||||
"RECIPE_CREATION_PATH": {
|
||||
"description": "API path to creation plugin. Default: empty",
|
||||
"name": "RECIPE_CREATION_PATH",
|
||||
"options": null,
|
||||
"type": "string",
|
||||
"value": ""
|
||||
},
|
||||
"brewfather_api_key": {
|
||||
"description": "Brewfather API Kay",
|
||||
"name": "brewfather_api_key",
|
||||
"options": null,
|
||||
"type": "string",
|
||||
"value": ""
|
||||
},
|
||||
"brewfather_user_id": {
|
||||
"description": "Brewfather User ID",
|
||||
"name": "brewfather_user_id",
|
||||
"options": null,
|
||||
"type": "string",
|
||||
"value": ""
|
||||
},
|
||||
"TEMP_UNIT": {
|
||||
"description": "Temperature Unit",
|
||||
"name": "TEMP_UNIT",
|
||||
"options": [
|
||||
{
|
||||
"label": "C",
|
||||
"value": "C"
|
||||
},
|
||||
{
|
||||
"label": "F",
|
||||
"value": "F"
|
||||
}
|
||||
],
|
||||
"type": "select",
|
||||
"value": "C"
|
||||
},
|
||||
"AutoMode": {
|
||||
"description": "Use AutoMode in steps",
|
||||
"name": "AutoMode",
|
||||
"options": [
|
||||
{
|
||||
"label": "Yes",
|
||||
"value": "Yes"
|
||||
},
|
||||
{
|
||||
"label": "No",
|
||||
"value": "No"
|
||||
}
|
||||
],
|
||||
"type": "select",
|
||||
"value": "Yes"
|
||||
},
|
||||
"steps_boil": {
|
||||
"description": "Boil step type",
|
||||
"name": "steps_boil",
|
||||
"options": null,
|
||||
"type": "step",
|
||||
"value": "BoilStep"
|
||||
},
|
||||
"steps_boil_temp": {
|
||||
"description": "Default Boil Temperature for Recipe Creation",
|
||||
"name": "steps_boil_temp",
|
||||
"options": null,
|
||||
"type": "number",
|
||||
"value": "99"
|
||||
},
|
||||
"steps_cooldown": {
|
||||
"description": "Cooldown step type",
|
||||
"name": "steps_cooldown",
|
||||
"options": null,
|
||||
"type": "step",
|
||||
"value": "CooldownStep"
|
||||
},
|
||||
"steps_cooldown_sensor": {
|
||||
"description": "Alternative Sensor to monitor temperature durring cooldown (if not selected, Kettle Sensor will be used)",
|
||||
"name": "steps_cooldown_sensor",
|
||||
"options": null,
|
||||
"type": "sensor",
|
||||
"value": ""
|
||||
},
|
||||
"steps_cooldown_temp": {
|
||||
"description": "Cooldown temp will send notification when this temeprature is reached",
|
||||
"name": "steps_cooldown_temp",
|
||||
"options": null,
|
||||
"type": "number",
|
||||
"value": "20"
|
||||
},
|
||||
"steps_mash": {
|
||||
"description": "Mash step type",
|
||||
"name": "steps_mash",
|
||||
"options": null,
|
||||
"type": "step",
|
||||
"value": "MashStep"
|
||||
},
|
||||
"steps_mashin": {
|
||||
"description": "MashIn step type",
|
||||
"name": "steps_mashin",
|
||||
"options": null,
|
||||
"type": "step",
|
||||
"value": "MashInStep"
|
||||
},
|
||||
"steps_mashout": {
|
||||
"description": "MashOut step type",
|
||||
"name": "steps_mashout",
|
||||
"options": null,
|
||||
"type": "step",
|
||||
"value": "NotificationStep"
|
||||
}
|
||||
}
|
20
tests/cbpi-test-config/config.yaml
Normal file
20
tests/cbpi-test-config/config.yaml
Normal file
|
@ -0,0 +1,20 @@
|
|||
|
||||
name: CraftBeerPi
|
||||
version: 4.0.8
|
||||
|
||||
index_url: /cbpi_ui/static/index.html
|
||||
|
||||
port: 8000
|
||||
|
||||
mqtt: false
|
||||
mqtt_host: localhost
|
||||
mqtt_port: 1883
|
||||
mqtt_username: ""
|
||||
mqtt_password: ""
|
||||
|
||||
username: cbpi
|
||||
password: 123
|
||||
|
||||
plugins:
|
||||
- cbpi4ui
|
||||
|
9
tests/cbpi-test-config/craftbeerpi.service
Normal file
9
tests/cbpi-test-config/craftbeerpi.service
Normal file
|
@ -0,0 +1,9 @@
|
|||
[Unit]
|
||||
Description=Craftbeer Pi
|
||||
|
||||
[Service]
|
||||
WorkingDirectory=/home/pi
|
||||
ExecStart=/usr/local/bin/cbpi start
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
62
tests/cbpi-test-config/craftbeerpiboot
Normal file
62
tests/cbpi-test-config/craftbeerpiboot
Normal file
|
@ -0,0 +1,62 @@
|
|||
#!/bin/sh
|
||||
|
||||
### BEGIN INIT INFO
|
||||
# Provides: craftbeerpi
|
||||
# Required-Start: $remote_fs $syslog
|
||||
# Required-Stop: $remote_fs $syslog
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: Put a short description of the service here
|
||||
# Description: Put a long description of the service here
|
||||
### END INIT INFO
|
||||
|
||||
# Change the next 3 lines to suit where you install your script and what you want to call it
|
||||
DIR=#DIR#
|
||||
DAEMON=$DIR/cbpi
|
||||
DAEMON_NAME=CraftBeerPI
|
||||
|
||||
# Add any command line options for your daemon here
|
||||
DAEMON_OPTS=""
|
||||
|
||||
# This next line determines what user the script runs as.
|
||||
# Root generally not recommended but necessary if you are using the Raspberry Pi GPIO from Python.
|
||||
DAEMON_USER=root
|
||||
|
||||
# The process ID of the script when it runs is stored here:
|
||||
PIDFILE=/var/run/$DAEMON_NAME.pid
|
||||
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
do_start () {
|
||||
log_daemon_msg "Starting system $DAEMON_NAME daemon"
|
||||
start-stop-daemon --start --background --pidfile $PIDFILE --make-pidfile --user $DAEMON_USER --chuid $DAEMON_USER --chdir $DIR --startas $DAEMON -- $DAEMON_OPTS
|
||||
log_end_msg $?
|
||||
}
|
||||
do_stop () {
|
||||
log_daemon_msg "Stopping system $DAEMON_NAME daemon"
|
||||
start-stop-daemon --stop --pidfile $PIDFILE --retry 10
|
||||
log_end_msg $?
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
|
||||
start|stop)
|
||||
do_${1}
|
||||
;;
|
||||
|
||||
restart|reload|force-reload)
|
||||
do_stop
|
||||
do_start
|
||||
;;
|
||||
|
||||
status)
|
||||
status_of_proc "$DAEMON_NAME" "$DAEMON" && exit 0 || exit $?
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Usage: /etc/init.d/$DAEMON_NAME {start|stop|restart|status}"
|
||||
exit 1
|
||||
;;
|
||||
|
||||
esac
|
||||
exit 0
|
3
tests/cbpi-test-config/dashboard/cbpi_dashboard_1.json
Normal file
3
tests/cbpi-test-config/dashboard/cbpi_dashboard_1.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"elements": []
|
||||
}
|
0
tests/cbpi-test-config/dashboard/widgets/.empty
Normal file
0
tests/cbpi-test-config/dashboard/widgets/.empty
Normal file
5
tests/cbpi-test-config/fermenter_data.json
Normal file
5
tests/cbpi-test-config/fermenter_data.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"data": [
|
||||
|
||||
]
|
||||
}
|
5
tests/cbpi-test-config/kettle.json
Normal file
5
tests/cbpi-test-config/kettle.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"data": [
|
||||
|
||||
]
|
||||
}
|
3
tests/cbpi-test-config/plugin_list.txt
Normal file
3
tests/cbpi-test-config/plugin_list.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
cbpi4-ui:
|
||||
installation_date: '2021-01-06 16:03:31'
|
||||
version: '0.0.1'
|
0
tests/cbpi-test-config/recipes/.empty
Normal file
0
tests/cbpi-test-config/recipes/.empty
Normal file
5
tests/cbpi-test-config/sensor.json
Normal file
5
tests/cbpi-test-config/sensor.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"data": [
|
||||
|
||||
]
|
||||
}
|
BIN
tests/cbpi-test-config/splash.png
Normal file
BIN
tests/cbpi-test-config/splash.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.9 MiB |
8
tests/cbpi-test-config/step_data.json
Normal file
8
tests/cbpi-test-config/step_data.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"basic": {
|
||||
"name": ""
|
||||
},
|
||||
"steps": [
|
||||
|
||||
]
|
||||
}
|
0
tests/cbpi-test-config/upload/.empty
Normal file
0
tests/cbpi-test-config/upload/.empty
Normal file
23
tests/cbpi_config_fixture.py
Normal file
23
tests/cbpi_config_fixture.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
# content of conftest.py
|
||||
from codecs import ignore_errors
|
||||
from distutils.command.config import config
|
||||
import os
|
||||
from cbpi.configFolder import ConfigFolder
|
||||
from cbpi.craftbeerpi import CraftBeerPi
|
||||
from aiohttp.test_utils import AioHTTPTestCase
|
||||
from distutils.dir_util import copy_tree
|
||||
|
||||
|
||||
class CraftBeerPiTestCase(AioHTTPTestCase):
|
||||
|
||||
async def get_application(self):
|
||||
self.config_folder = self.configuration()
|
||||
self.cbpi = CraftBeerPi(self.config_folder)
|
||||
await self.cbpi.init_serivces()
|
||||
return self.cbpi.app
|
||||
|
||||
def configuration(self):
|
||||
test_directory = os.path.dirname(__file__)
|
||||
test_config_directory = os.path.join(test_directory, 'cbpi-test-config')
|
||||
configFolder = ConfigFolder(test_config_directory)
|
||||
return configFolder
|
Binary file not shown.
|
@ -1,66 +1,39 @@
|
|||
import logging
|
||||
from unittest import mock
|
||||
from unittest.mock import MagicMock, Mock
|
||||
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
|
||||
from cbpi.craftbeerpi import CraftBeerPi
|
||||
from aiohttp.test_utils import unittest_run_loop
|
||||
from tests.cbpi_config_fixture import CraftBeerPiTestCase
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
|
||||
|
||||
|
||||
class ActorTestCase(AioHTTPTestCase):
|
||||
|
||||
async def get_application(self):
|
||||
self.cbpi = CraftBeerPi()
|
||||
await self.cbpi.init_serivces()
|
||||
return self.cbpi.app
|
||||
|
||||
@unittest_run_loop
|
||||
async def test_actor_mock(self):
|
||||
with mock.patch.object(self.cbpi.bus, 'fire', wraps=self.cbpi.bus.fire) as mock_obj:
|
||||
# Send HTTP POST
|
||||
resp = await self.client.request("POST", "/actor/1/on")
|
||||
# Check Result
|
||||
assert resp.status == 204
|
||||
# Check if Event are fired
|
||||
assert mock_obj.call_count == 2
|
||||
|
||||
class ActorTestCase(CraftBeerPiTestCase):
|
||||
|
||||
@unittest_run_loop
|
||||
async def test_actor_switch(self):
|
||||
|
||||
resp = await self.client.post(path="/login", data={"username": "cbpi", "password": "123"})
|
||||
assert resp.status == 200
|
||||
assert resp.status == 200, "login should be successful"
|
||||
|
||||
resp = await self.client.request("POST", "/actor/1/on")
|
||||
assert resp.status == 204
|
||||
i = await self.cbpi.actor.get_one(1)
|
||||
resp = await self.client.request("POST", "/actor/3CUJte4bkxDMFCtLX8eqsX/on")
|
||||
assert resp.status == 204, "switching actor on should work"
|
||||
i = self.cbpi.actor.find_by_id("3CUJte4bkxDMFCtLX8eqsX")
|
||||
|
||||
assert i.instance.state is True
|
||||
|
||||
resp = await self.client.request("POST", "/actor/1/off")
|
||||
resp = await self.client.request("POST", "/actor/3CUJte4bkxDMFCtLX8eqsX/off")
|
||||
assert resp.status == 204
|
||||
i = await self.cbpi.actor.get_one(1)
|
||||
assert i.instance.state is False
|
||||
|
||||
resp = await self.client.request("POST", "/actor/1/toggle")
|
||||
|
||||
assert resp.status == 204
|
||||
i = await self.cbpi.actor.get_one(1)
|
||||
assert i.instance.state is True
|
||||
|
||||
resp = await self.client.request("POST", "/actor/1/toggle")
|
||||
assert resp.status == 204
|
||||
i = await self.cbpi.actor.get_one(1)
|
||||
i = self.cbpi.actor.find_by_id("3CUJte4bkxDMFCtLX8eqsX")
|
||||
assert i.instance.state is False
|
||||
|
||||
@unittest_run_loop
|
||||
async def test_crud(self):
|
||||
data = {
|
||||
"name": "CustomActor",
|
||||
"type": "CustomActor",
|
||||
"config": {
|
||||
"interval": 5
|
||||
}
|
||||
"name": "SomeActor",
|
||||
"power": 100,
|
||||
"props": {
|
||||
},
|
||||
"state": False,
|
||||
"type": "DummyActor"
|
||||
}
|
||||
|
||||
# Add new sensor
|
||||
|
|
|
@ -1,20 +1,16 @@
|
|||
import logging
|
||||
import unittest
|
||||
from cbpi.cli import CraftBeerPiCli
|
||||
|
||||
from cli import add, remove, list_plugins
|
||||
from cbpi.configFolder import ConfigFolder
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
|
||||
|
||||
|
||||
class CLITest(unittest.TestCase):
|
||||
|
||||
def test_install(self):
|
||||
assert add("cbpi4-ui-plugin") == True
|
||||
assert add("cbpi4-ui-plugin") == False
|
||||
assert remove("cbpi4-ui-plugin") == True
|
||||
|
||||
def test_list(self):
|
||||
list_plugins()
|
||||
cli = CraftBeerPiCli(ConfigFolder("./cbpi-test-config"))
|
||||
cli.plugins_list()
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
|
@ -1,54 +1,31 @@
|
|||
import time
|
||||
|
||||
import aiosqlite
|
||||
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
|
||||
from cbpi.api.config import ConfigType
|
||||
|
||||
from cbpi.craftbeerpi import CraftBeerPi
|
||||
|
||||
|
||||
class ConfigTestCase(AioHTTPTestCase):
|
||||
|
||||
|
||||
async def get_application(self):
|
||||
self.cbpi = CraftBeerPi()
|
||||
await self.cbpi.init_serivces()
|
||||
return self.cbpi.app
|
||||
from aiohttp.test_utils import unittest_run_loop
|
||||
from tests.cbpi_config_fixture import CraftBeerPiTestCase
|
||||
|
||||
class ConfigTestCase(CraftBeerPiTestCase):
|
||||
|
||||
@unittest_run_loop
|
||||
async def test_get(self):
|
||||
|
||||
assert self.cbpi.config.get("CBPI_TEST_1", 1) == "22"
|
||||
assert self.cbpi.config.get("steps_boil_temp", 1) == "99"
|
||||
|
||||
@unittest_run_loop
|
||||
async def test_set_get(self):
|
||||
value = str(time.time())
|
||||
value = 35
|
||||
|
||||
await self.cbpi.config.set("CBPI_TEST_2", value)
|
||||
|
||||
assert self.cbpi.config.get("CBPI_TEST_2", 1) == value
|
||||
|
||||
@unittest_run_loop
|
||||
async def test_add(self):
|
||||
value = str(time.time())
|
||||
key = "CBPI_TEST_3"
|
||||
async with aiosqlite.connect("./craftbeerpi.db") as db:
|
||||
await db.execute("DELETE FROM config WHERE name = ? ", (key,))
|
||||
await db.commit()
|
||||
|
||||
await self.cbpi.config.add(key, value, type=ConfigType.STRING, description="test")
|
||||
await self.cbpi.config.set("steps_cooldown_temp", value)
|
||||
assert self.cbpi.config.get("steps_cooldown_temp", 1) == value
|
||||
|
||||
@unittest_run_loop
|
||||
async def test_http_set(self):
|
||||
value = str(time.time())
|
||||
key = "CBPI_TEST_3"
|
||||
await self.cbpi.config.set(key, value)
|
||||
assert self.cbpi.config.get(key, 1) == value
|
||||
value = "Some New Brewery Name"
|
||||
key = "BREWERY_NAME"
|
||||
|
||||
resp = await self.client.request("PUT", "/config/%s/" % key, json={'value': '1'})
|
||||
resp = await self.client.request("PUT", "/config/%s/" % key, json={'value': value})
|
||||
assert resp.status == 204
|
||||
assert self.cbpi.config.get(key, -1) == "1"
|
||||
|
||||
assert self.cbpi.config.get(key, -1) == value
|
||||
|
||||
@unittest_run_loop
|
||||
async def test_http_get(self):
|
||||
|
@ -57,5 +34,5 @@ class ConfigTestCase(AioHTTPTestCase):
|
|||
|
||||
@unittest_run_loop
|
||||
async def test_get_default(self):
|
||||
value = self.cbpi.config.get("HELLO_WORLD", None)
|
||||
assert value == None
|
||||
value = self.cbpi.config.get("HELLO_WORLD", "DefaultValue")
|
||||
assert value == "DefaultValue"
|
|
@ -1,18 +1,10 @@
|
|||
import aiohttp
|
||||
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
|
||||
from aiohttp.test_utils import unittest_run_loop
|
||||
from tests.cbpi_config_fixture import CraftBeerPiTestCase
|
||||
|
||||
from cbpi.craftbeerpi import CraftBeerPi
|
||||
|
||||
|
||||
class DashboardTestCase(AioHTTPTestCase):
|
||||
|
||||
|
||||
|
||||
|
||||
async def get_application(self):
|
||||
self.cbpi = CraftBeerPi()
|
||||
await self.cbpi.init_serivces()
|
||||
return self.cbpi.app
|
||||
class DashboardTestCase(CraftBeerPiTestCase):
|
||||
|
||||
@unittest_run_loop
|
||||
async def test_crud(self):
|
||||
|
@ -28,56 +20,12 @@ class DashboardTestCase(AioHTTPTestCase):
|
|||
"config": {}
|
||||
}
|
||||
|
||||
resp = await self.client.get(path="/dashboard")
|
||||
resp = await self.client.get(path="/dashboard/current")
|
||||
assert resp.status == 200
|
||||
|
||||
# Add new dashboard
|
||||
resp = await self.client.post(path="/dashboard/", json=data)
|
||||
assert resp.status == 200
|
||||
|
||||
m = await resp.json()
|
||||
dashboard_id = m["id"]
|
||||
|
||||
# Get dashboard
|
||||
resp = await self.client.get(path="/dashboard/%s" % dashboard_id)
|
||||
assert resp.status == 200
|
||||
|
||||
m2 = await resp.json()
|
||||
dashboard_id = m2["id"]
|
||||
|
||||
# Update dashboard
|
||||
resp = await self.client.put(path="/dashboard/%s" % dashboard_id, json=m)
|
||||
assert resp.status == 200
|
||||
dashboard_id = await resp.json()
|
||||
|
||||
# Add dashboard content
|
||||
dashboard_content["dbid"] = dashboard_id
|
||||
resp = await self.client.post(path="/dashboard/%s/content" % dashboard_id, json=dashboard_content)
|
||||
assert resp.status == 200
|
||||
m_content = await resp.json()
|
||||
print("CONTENT", m_content)
|
||||
content_id = m_content["id"]
|
||||
# Get dashboard
|
||||
resp = await self.client.get(path="/dashboard/%s/content" % (dashboard_id))
|
||||
assert resp.status == 200
|
||||
|
||||
|
||||
resp = await self.client.post(path="/dashboard/%s/content/%s/move" % (dashboard_id, content_id), json=dict(x=1,y=1))
|
||||
assert resp.status == 200
|
||||
|
||||
resp = await self.client.delete(path="/dashboard/%s/content/%s" % (dashboard_id, content_id))
|
||||
assert resp.status == 204
|
||||
|
||||
# Delete dashboard
|
||||
resp = await self.client.delete(path="/dashboard/%s" % dashboard_id)
|
||||
assert resp.status == 204
|
||||
|
||||
@unittest_run_loop
|
||||
async def test_dashboard_controller(self):
|
||||
result = await self.cbpi.dashboard.get_all()
|
||||
print(result)
|
||||
|
||||
await self.cbpi.dashboard.add(**{"name":"Tewst"})
|
||||
print(await self.cbpi.dashboard.get_one(1))
|
||||
|
||||
await self.cbpi.dashboard.add_content(dict(dbid=1,element_id=1,type="test",config={"name":"Manue"}))
|
||||
await self.cbpi.dashboard.move_content(1,2,3)
|
|
@ -1,116 +0,0 @@
|
|||
import asyncio
|
||||
from cbpi.api.dataclasses import Fermenter, FermenterStep, Props, Step
|
||||
import logging
|
||||
from unittest import mock
|
||||
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
|
||||
from cbpi.craftbeerpi import CraftBeerPi
|
||||
from cbpi.controller.fermentation_controller import FermenationController
|
||||
import unittest
|
||||
import json
|
||||
from aiohttp import web
|
||||
from unittest.mock import MagicMock, Mock
|
||||
logging.basicConfig(level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
|
||||
|
||||
|
||||
class FermenterTest(AioHTTPTestCase):
|
||||
|
||||
async def get_application(self):
|
||||
app = web.Application()
|
||||
return app
|
||||
|
||||
def create_file(self):
|
||||
|
||||
data = [
|
||||
{
|
||||
"id": "f1",
|
||||
"name": "Fermenter1",
|
||||
"props": {},
|
||||
"steps": [
|
||||
{
|
||||
"id": "f1s1",
|
||||
"name": "Step1",
|
||||
"props": {},
|
||||
"state_text": "",
|
||||
"status": "I",
|
||||
"type": "T2"
|
||||
},
|
||||
{
|
||||
"id": "f1s2",
|
||||
"name": "Step2",
|
||||
"props": {},
|
||||
"state_text": "",
|
||||
"status": "I",
|
||||
"type": "T1"
|
||||
},
|
||||
],
|
||||
"target_temp": 0
|
||||
},
|
||||
{
|
||||
"id": "f2",
|
||||
"name": "Fermenter2",
|
||||
"props": {},
|
||||
"steps": [
|
||||
{
|
||||
"id": "f2s1",
|
||||
"name": "Step1",
|
||||
"props": {},
|
||||
"state_text": "",
|
||||
"status": "I",
|
||||
"type": "T1"
|
||||
},
|
||||
{
|
||||
"id": "f2s2",
|
||||
"name": "Step2",
|
||||
"props": {},
|
||||
"state_text": "",
|
||||
"status": "I",
|
||||
"type": "T2"
|
||||
},
|
||||
],
|
||||
"target_temp": 0
|
||||
}
|
||||
]
|
||||
|
||||
with open("./config/fermenter_data.json", "w") as file:
|
||||
json.dump(data, file, indent=4, sort_keys=True)
|
||||
|
||||
|
||||
@unittest_run_loop
|
||||
async def test_actor_mock(self):
|
||||
self.create_file()
|
||||
mock = Mock()
|
||||
f = FermenationController(mock)
|
||||
|
||||
f.types = {
|
||||
"T1": {"name": "T2", "class": FermenterStep, "properties": [], "actions": []},
|
||||
"T2": {"name": "T2", "class": FermenterStep, "properties": [], "actions": []}
|
||||
}
|
||||
await f.load()
|
||||
#ferm = Fermenter(name="Maneul")
|
||||
# item = await f.create(ferm)
|
||||
# await f.create_step(item.id, Step(name="Manuel"))
|
||||
# await f.delete(item.id)
|
||||
|
||||
item = await f.get("f1")
|
||||
|
||||
await f.start("f1")
|
||||
await f.start("f2")
|
||||
await asyncio.sleep(3)
|
||||
# await f.create_step(item.id, Step(name="MANUEL", props=Props()))
|
||||
|
||||
#await f.start(item.id)
|
||||
#await asyncio.sleep(1)
|
||||
#await f.next(item.id)
|
||||
#await asyncio.sleep(1)
|
||||
#await f.next(item.id)
|
||||
#await asyncio.sleep(1)
|
||||
#await f.next(item.id)
|
||||
#await asyncio.sleep(1)
|
||||
#await f.move_step("f1", "f1s1", 1)
|
||||
# await f.reset(item.id)
|
||||
await f.shutdown()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
|
@ -15,13 +15,6 @@ except Exception:
|
|||
patcher.start()
|
||||
import RPi.GPIO as GPIO
|
||||
|
||||
|
||||
class HelloWorld(object):
|
||||
|
||||
def test(self, a):
|
||||
return a
|
||||
|
||||
|
||||
class TestSwitch(unittest.TestCase):
|
||||
|
||||
GPIO_NUM = 22
|
||||
|
@ -36,14 +29,3 @@ class TestSwitch(unittest.TestCase):
|
|||
def test_switch_without_scheduler_starts_disabled(self, patched_output):
|
||||
GPIO.output(self.GPIO_NUM, GPIO.LOW)
|
||||
patched_output.assert_called_once_with(self.GPIO_NUM, GPIO.LOW)
|
||||
|
||||
|
||||
def test_hello_world(self):
|
||||
h = HelloWorld()
|
||||
with mock.patch.object(HelloWorld, 'test', wraps=h.test) as fake_increment:
|
||||
#print(h.test("HALLO"))
|
||||
print(h.test("ABC"))
|
||||
print(fake_increment.call_args)
|
||||
print(h.test("HALLO"))
|
||||
print(fake_increment.call_args_list)
|
||||
|
||||
|
|
|
@ -1,14 +1,8 @@
|
|||
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
|
||||
|
||||
from cbpi.craftbeerpi import CraftBeerPi
|
||||
from aiohttp.test_utils import unittest_run_loop
|
||||
from tests.cbpi_config_fixture import CraftBeerPiTestCase
|
||||
|
||||
|
||||
class IndexTestCase(AioHTTPTestCase):
|
||||
|
||||
async def get_application(self):
|
||||
self.cbpi = CraftBeerPi()
|
||||
await self.cbpi.init_serivces()
|
||||
return self.cbpi.app
|
||||
class IndexTestCase(CraftBeerPiTestCase):
|
||||
|
||||
@unittest_run_loop
|
||||
async def test_index(self):
|
||||
|
|
|
@ -1,52 +1,16 @@
|
|||
import asyncio
|
||||
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
|
||||
from cbpi.craftbeerpi import CraftBeerPi
|
||||
from aiohttp.test_utils import unittest_run_loop
|
||||
from tests.cbpi_config_fixture import CraftBeerPiTestCase
|
||||
|
||||
|
||||
class KettleTestCase(AioHTTPTestCase):
|
||||
|
||||
|
||||
async def get_application(self):
|
||||
self.cbpi = CraftBeerPi()
|
||||
await self.cbpi.init_serivces()
|
||||
return self.cbpi.app
|
||||
class KettleTestCase(CraftBeerPiTestCase):
|
||||
|
||||
@unittest_run_loop
|
||||
async def test_get(self):
|
||||
|
||||
resp = await self.client.request("GET", "/kettle")
|
||||
assert resp.status == 200
|
||||
print(await resp.json())
|
||||
|
||||
@unittest_run_loop
|
||||
async def test_heater(self):
|
||||
resp = await self.client.get("/kettle/1/heater/on")
|
||||
assert resp.status == 204
|
||||
|
||||
resp = await self.client.get("/kettle/1/heater/off")
|
||||
assert resp.status == 204
|
||||
|
||||
@unittest_run_loop
|
||||
async def test_agitator(self):
|
||||
resp = await self.client.get("/kettle/1/agitator/on")
|
||||
assert resp.status == 204
|
||||
|
||||
resp = await self.client.get("/kettle/1/agitator/off")
|
||||
assert resp.status == 204
|
||||
|
||||
@unittest_run_loop
|
||||
async def test_temp(self):
|
||||
resp = await self.client.get("/kettle/1/temp")
|
||||
assert resp.status == 204
|
||||
|
||||
resp = await self.client.get("/kettle/1/targettemp")
|
||||
assert resp.status == 200
|
||||
|
||||
@unittest_run_loop
|
||||
async def test_automatic(self):
|
||||
resp = await self.client.post("/kettle/1/automatic")
|
||||
assert resp.status == 204
|
||||
|
||||
kettle = resp.json()
|
||||
assert kettle != None
|
||||
|
||||
@unittest_run_loop
|
||||
async def test_crud(self):
|
||||
|
@ -69,21 +33,21 @@ class KettleTestCase(AioHTTPTestCase):
|
|||
|
||||
m = await resp.json()
|
||||
|
||||
sensor_id = m["id"]
|
||||
kettle_id = m["id"]
|
||||
print("KETTLE", m["id"], m)
|
||||
# Get sensor
|
||||
resp = await self.client.get(path="/kettle/%s" % sensor_id)
|
||||
|
||||
# Update Kettle
|
||||
resp = await self.client.put(path="/kettle/%s" % kettle_id, json=m)
|
||||
assert resp.status == 200
|
||||
|
||||
m2 = await resp.json()
|
||||
sensor_id = m2["id"]
|
||||
|
||||
# Update Sensor
|
||||
resp = await self.client.put(path="/kettle/%s" % sensor_id, json=m)
|
||||
assert resp.status == 200
|
||||
# Set Kettle target temp
|
||||
resp = await self.client.post(path="/kettle/%s/target_temp" % kettle_id, json={"temp":75})
|
||||
assert resp.status == 204
|
||||
|
||||
# # Delete Sensor
|
||||
resp = await self.client.delete(path="/kettle/%s" % sensor_id)
|
||||
resp = await self.client.delete(path="/kettle/%s" % kettle_id)
|
||||
assert resp.status == 204
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,21 +1,16 @@
|
|||
import asyncio
|
||||
import glob
|
||||
|
||||
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
|
||||
from aiohttp.test_utils import unittest_run_loop
|
||||
from tests.cbpi_config_fixture import CraftBeerPiTestCase
|
||||
import os
|
||||
|
||||
from cbpi.craftbeerpi import CraftBeerPi, load_config
|
||||
|
||||
|
||||
class UtilsTestCase(AioHTTPTestCase):
|
||||
|
||||
async def get_application(self):
|
||||
self.cbpi = CraftBeerPi()
|
||||
await self.cbpi.init_serivces()
|
||||
return self.cbpi.app
|
||||
class LoggerTestCase(CraftBeerPiTestCase):
|
||||
|
||||
@unittest_run_loop
|
||||
async def test_log_data(self):
|
||||
|
||||
os.makedirs("./logs", exist_ok=True)
|
||||
log_name = "test"
|
||||
#clear all logs
|
||||
self.cbpi.log.clear_log(log_name)
|
||||
|
|
|
@ -1,16 +1,8 @@
|
|||
import aiohttp
|
||||
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
|
||||
|
||||
from cbpi.craftbeerpi import CraftBeerPi
|
||||
from aiohttp.test_utils import unittest_run_loop
|
||||
from tests.cbpi_config_fixture import CraftBeerPiTestCase
|
||||
|
||||
|
||||
class NotificationTestCase(AioHTTPTestCase):
|
||||
|
||||
async def get_application(self):
|
||||
self.cbpi = CraftBeerPi()
|
||||
await self.cbpi.init_serivces()
|
||||
return self.cbpi.app
|
||||
|
||||
class NotificationTestCase(CraftBeerPiTestCase):
|
||||
|
||||
@unittest_run_loop
|
||||
async def test_actor_switch(self):
|
||||
|
|
|
@ -1,14 +1,8 @@
|
|||
import asyncio
|
||||
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
|
||||
from cbpi.craftbeerpi import CraftBeerPi
|
||||
from aiohttp.test_utils import unittest_run_loop
|
||||
from tests.cbpi_config_fixture import CraftBeerPiTestCase
|
||||
|
||||
|
||||
class SensorTestCase(AioHTTPTestCase):
|
||||
|
||||
async def get_application(self):
|
||||
self.cbpi = CraftBeerPi()
|
||||
await self.cbpi.init_serivces()
|
||||
return self.cbpi.app
|
||||
class SensorTestCase(CraftBeerPiTestCase):
|
||||
|
||||
@unittest_run_loop
|
||||
async def test_crud(self):
|
||||
|
@ -28,12 +22,11 @@ class SensorTestCase(AioHTTPTestCase):
|
|||
m = await resp.json()
|
||||
sensor_id = m["id"]
|
||||
|
||||
# Get sensor
|
||||
# Get sensor value
|
||||
resp = await self.client.get(path="/sensor/%s"% sensor_id)
|
||||
assert resp.status == 200
|
||||
|
||||
m2 = await resp.json()
|
||||
sensor_id = m2["id"]
|
||||
|
||||
# Update Sensor
|
||||
resp = await self.client.put(path="/sensor/%s" % sensor_id, json=m)
|
||||
|
|
|
@ -1,23 +1,13 @@
|
|||
import asyncio
|
||||
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
|
||||
from cbpi.craftbeerpi import CraftBeerPi
|
||||
from aiohttp.test_utils import unittest_run_loop
|
||||
from tests.cbpi_config_fixture import CraftBeerPiTestCase
|
||||
|
||||
|
||||
class StepTestCase(AioHTTPTestCase):
|
||||
|
||||
async def get_application(self):
|
||||
self.cbpi = CraftBeerPi()
|
||||
await self.cbpi.init_serivces()
|
||||
return self.cbpi.app
|
||||
class StepTestCase(CraftBeerPiTestCase):
|
||||
|
||||
@unittest_run_loop
|
||||
async def test_get(self):
|
||||
|
||||
resp = await self.client.request("GET", "/step")
|
||||
print(resp)
|
||||
assert resp.status == 200
|
||||
|
||||
resp = await self.client.request("GET", "/step/types")
|
||||
resp = await self.client.request("GET", "/step2")
|
||||
print(resp)
|
||||
assert resp.status == 200
|
||||
|
||||
|
@ -31,26 +21,19 @@ class StepTestCase(AioHTTPTestCase):
|
|||
}
|
||||
|
||||
# Add new step
|
||||
resp = await self.client.post(path="/step/", json=data)
|
||||
resp = await self.client.post(path="/step2/", json=data)
|
||||
assert resp.status == 200
|
||||
|
||||
m = await resp.json()
|
||||
print("Step", m)
|
||||
sensor_id = m["id"]
|
||||
|
||||
# Get sensor
|
||||
resp = await self.client.get(path="/step/%s" % sensor_id)
|
||||
# Update step
|
||||
resp = await self.client.put(path="/step2/%s" % sensor_id, json=m)
|
||||
assert resp.status == 200
|
||||
|
||||
m2 = await resp.json()
|
||||
sensor_id = m2["id"]
|
||||
|
||||
# Update Sensor
|
||||
resp = await self.client.put(path="/step/%s" % sensor_id, json=m)
|
||||
assert resp.status == 200
|
||||
|
||||
# # Delete Sensor
|
||||
resp = await self.client.delete(path="/step/%s" % sensor_id)
|
||||
# # Delete step
|
||||
resp = await self.client.delete(path="/step2/%s" % sensor_id)
|
||||
assert resp.status == 204
|
||||
|
||||
def create_wait_callback(self, topic):
|
||||
|
@ -67,33 +50,3 @@ class StepTestCase(AioHTTPTestCase):
|
|||
|
||||
if future in done:
|
||||
pass
|
||||
|
||||
@unittest_run_loop
|
||||
async def test_process(self):
|
||||
|
||||
step_ctlr = self.cbpi.step
|
||||
|
||||
await step_ctlr.clear_all()
|
||||
await step_ctlr.add(**{"name": "Kettle1", "type": "CustomStepCBPi", "config": {"name1": "1", "temp": 99}})
|
||||
await step_ctlr.add(**{"name": "Kettle1", "type": "CustomStepCBPi", "config": {"name1": "1", "temp": 99}})
|
||||
await step_ctlr.add(**{"name": "Kettle1", "type": "CustomStepCBPi", "config": {"name1": "1", "temp": 99}})
|
||||
|
||||
await step_ctlr.stop_all()
|
||||
|
||||
future = self.create_wait_callback("step/+/started")
|
||||
await step_ctlr.start()
|
||||
await self.wait(future)
|
||||
|
||||
for i in range(len(step_ctlr.cache)-1):
|
||||
future = self.create_wait_callback("step/+/started")
|
||||
await step_ctlr.next()
|
||||
await self.wait(future)
|
||||
|
||||
await self.print_steps()
|
||||
|
||||
async def print_steps(self):
|
||||
|
||||
s = await self.cbpi.step.get_all()
|
||||
print(s)
|
||||
for k, v in s.items():
|
||||
print(k, v.to_json())
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
import logging
|
||||
from unittest import mock
|
||||
import unittest
|
||||
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
|
||||
from cbpi.craftbeerpi import CraftBeerPi
|
||||
import pprint
|
||||
import asyncio
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
|
||||
pp = pprint.PrettyPrinter(indent=4)
|
||||
|
||||
class ActorTestCase(AioHTTPTestCase):
|
||||
|
||||
async def get_application(self):
|
||||
self.cbpi = CraftBeerPi()
|
||||
await self.cbpi.init_serivces()
|
||||
return self.cbpi.app
|
||||
'''
|
||||
@unittest_run_loop
|
||||
async def test_get_all(self):
|
||||
resp = await self.client.get(path="/step2")
|
||||
assert resp.status == 200
|
||||
|
||||
@unittest_run_loop
|
||||
async def test_add_step(self):
|
||||
resp = await self.client.post(path="/step2", json=dict(name="Manuel"))
|
||||
data = await resp.json()
|
||||
assert resp.status == 200
|
||||
|
||||
@unittest_run_loop
|
||||
async def test_delete(self):
|
||||
|
||||
resp = await self.client.post(path="/step2", json=dict(name="Manuel"))
|
||||
data = await resp.json()
|
||||
assert resp.status == 200
|
||||
resp = await self.client.delete(path="/step2/%s" % data["id"])
|
||||
assert resp.status == 204
|
||||
'''
|
||||
|
||||
@unittest_run_loop
|
||||
async def test_move(self):
|
||||
await self.cbpi.step2.resume()
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
|
@ -1,19 +1,11 @@
|
|||
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
|
||||
|
||||
from cbpi.craftbeerpi import CraftBeerPi
|
||||
from aiohttp.test_utils import unittest_run_loop
|
||||
from tests.cbpi_config_fixture import CraftBeerPiTestCase
|
||||
|
||||
|
||||
class IndexTestCase(AioHTTPTestCase):
|
||||
|
||||
async def get_application(self):
|
||||
self.cbpi = CraftBeerPi()
|
||||
await self.cbpi.init_serivces()
|
||||
return self.cbpi.app
|
||||
class IndexTestCase(CraftBeerPiTestCase):
|
||||
|
||||
@unittest_run_loop
|
||||
async def test_endpoints(self):
|
||||
|
||||
|
||||
# Test Index Page
|
||||
resp = await self.client.post(path="/system/restart")
|
||||
assert resp.status == 200
|
||||
|
@ -21,9 +13,3 @@ class IndexTestCase(AioHTTPTestCase):
|
|||
resp = await self.client.post(path="/system/shutdown")
|
||||
assert resp.status == 200
|
||||
|
||||
resp = await self.client.get(path="/system/jobs")
|
||||
assert resp.status == 200
|
||||
|
||||
resp = await self.client.get(path="/system/events")
|
||||
assert resp.status == 200
|
||||
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
|
||||
|
||||
from cbpi.craftbeerpi import CraftBeerPi, load_config
|
||||
|
||||
|
||||
class UtilsTestCase(AioHTTPTestCase):
|
||||
|
||||
async def get_application(self):
|
||||
self.cbpi = CraftBeerPi()
|
||||
await self.cbpi.init_serivces()
|
||||
return self.cbpi.app
|
||||
|
||||
@unittest_run_loop
|
||||
async def test_load_file(self):
|
||||
assert load_config("") is None
|
||||
|
|
@ -1,56 +1,46 @@
|
|||
import asyncio
|
||||
|
||||
import aiohttp
|
||||
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
|
||||
from aiohttp.test_utils import unittest_run_loop
|
||||
from tests.cbpi_config_fixture import CraftBeerPiTestCase
|
||||
|
||||
from cbpi.craftbeerpi import CraftBeerPi
|
||||
# class WebSocketTestCase(CraftBeerPiTestCase):
|
||||
|
||||
# @unittest_run_loop
|
||||
# async def test_brewing_process(self):
|
||||
|
||||
class WebSocketTestCase(AioHTTPTestCase):
|
||||
# count_step_done = 0
|
||||
# async with self.client.ws_connect('/ws') as ws:
|
||||
# await ws.send_json(data=dict(topic="step/stop"))
|
||||
# await ws.send_json(data=dict(topic="step/start"))
|
||||
# async for msg in ws:
|
||||
# if msg.type == aiohttp.WSMsgType.TEXT:
|
||||
# try:
|
||||
# msg_obj = msg.json()
|
||||
# topic = msg_obj.get("topic")
|
||||
# if topic == "job/step/done":
|
||||
# count_step_done = count_step_done + 1
|
||||
# if topic == "step/brewing/finished":
|
||||
# await ws.send_json(data=dict(topic="close"))
|
||||
# except Exception as e:
|
||||
# print(e)
|
||||
# break
|
||||
# elif msg.type == aiohttp.WSMsgType.ERROR:
|
||||
# break
|
||||
|
||||
async def get_application(self):
|
||||
self.cbpi = CraftBeerPi()
|
||||
await self.cbpi.init_serivces()
|
||||
return self.cbpi.app
|
||||
# assert count_step_done == 4
|
||||
|
||||
# @unittest_run_loop
|
||||
# async def test_wrong_format(self):
|
||||
|
||||
@unittest_run_loop
|
||||
async def test_brewing_process(self):
|
||||
# async with self.client.ws_connect('/ws') as ws:
|
||||
# await ws.send_json(data=dict(a="close"))
|
||||
# async for msg in ws:
|
||||
# print("MSG TYP", msg.type, msg.data)
|
||||
# if msg.type == aiohttp.WSMsgType.TEXT:
|
||||
# msg_obj = msg.json()
|
||||
# if msg_obj["topic"] != "connection/success":
|
||||
# print(msg.data)
|
||||
# raise Exception()
|
||||
|
||||
count_step_done = 0
|
||||
async with self.client.ws_connect('/ws') as ws:
|
||||
await ws.send_json(data=dict(topic="step/stop"))
|
||||
await ws.send_json(data=dict(topic="step/start"))
|
||||
async for msg in ws:
|
||||
if msg.type == aiohttp.WSMsgType.TEXT:
|
||||
try:
|
||||
msg_obj = msg.json()
|
||||
topic = msg_obj.get("topic")
|
||||
if topic == "job/step/done":
|
||||
count_step_done = count_step_done + 1
|
||||
if topic == "step/brewing/finished":
|
||||
await ws.send_json(data=dict(topic="close"))
|
||||
except Exception as e:
|
||||
print(e)
|
||||
break
|
||||
elif msg.type == aiohttp.WSMsgType.ERROR:
|
||||
break
|
||||
|
||||
assert count_step_done == 4
|
||||
|
||||
@unittest_run_loop
|
||||
async def test_wrong_format(self):
|
||||
|
||||
async with self.client.ws_connect('/ws') as ws:
|
||||
await ws.send_json(data=dict(a="close"))
|
||||
async for msg in ws:
|
||||
print("MSG TYP", msg.type, msg.data)
|
||||
if msg.type == aiohttp.WSMsgType.TEXT:
|
||||
msg_obj = msg.json()
|
||||
if msg_obj["topic"] != "connection/success":
|
||||
print(msg.data)
|
||||
raise Exception()
|
||||
|
||||
else:
|
||||
raise Exception()
|
||||
# else:
|
||||
# raise Exception()
|
||||
|
||||
|
|
Loading…
Reference in a new issue