51 Commits
Author SHA1 Message Date
Manuel Fritsch 25cf97da58 fermenter set to -20 2020-12-20 19:37:51 +01:00
Manuel83andGitHub 2b095c8c77 Update install.sh
WiringPi Removed
2019-08-20 21:17:46 +02:00
Manuel83andGitHub 63f9f5468b Update __init__.py 2019-08-08 21:29:01 +02:00
Manuel83andGitHub 16ad8daf44 Change Target Temp for Fermentation to 50
Change Target Temp for Fermentation to 50
2019-06-18 22:21:28 +02:00
Manuel83andGitHub 67a103d1f3 Update schema.sql 2019-05-18 10:09:31 +02:00
Manuel83andGitHub 274f282e11 Update __init__.py 2019-05-18 10:08:32 +02:00
manuel83 4e432e05bd pyyaml update 2019-02-07 21:24:31 +01:00
Manuel83 eda209422f Update package because of vulnerability
Flask:
CVE-2018-1000656 More information
moderate severity
Vulnerable versions: < 0.12.3
Patched version: 0.12.3
The Pallets Project flask version Before 0.12.3 contains a CWE-20:
Improper Input Validation vulnerability in flask that can result in
Large amount of memory usage possibly leading to denial of service.
This attack appear to be exploitable via Attacker provides JSON data in
incorrect encoding. This vulnerability appears to have been fixed in
0.12.3.

Requests:
CVE-2018-18074 More information
moderate severity
Vulnerable versions: <= 2.19.1
Patched version: 2.20.0
The Requests package through 2.19.1 before 2018-09-14 for Python sends
an HTTP Authorization header to an http URI upon receiving a
same-hostname https-to-http redirect, which makes it easier for remote
attackers to discover credentials by sniffing the network.
2018-10-29 22:15:03 +01:00
Manuel83andGitHub 5b47b94833 Merge pull request #213 from cgspeck/fix-typo
Fix typo in README
2018-09-04 06:32:32 +02:00
Manuel83andGitHub 98409d9895 Merge pull request #206 from swimIan/swimIan-patch-1
Remove "Steps started on xml imported" bug
2018-09-04 06:32:15 +02:00
Chris Speck 433b1c30f1 Fix typo which resulted in reference to incorrect port 2018-08-15 22:47:36 +10:00
Manuel83andGitHub a477cb83a8 Merge pull request #211 from cgspeck/docker-dev-env
Docker files for ease of development on PCs/macs
2018-08-15 07:00:19 +02:00
Chris Speck a22dcba77c Docker files for ease of development on PCs/macs 2018-08-13 23:13:13 +10:00
swimIanandGitHub c1d94a8f61 Remove "Steps started on xml imported" bug 2018-08-03 09:12:45 -07:00
swimIanandGitHub dd64f4ee16 Merge pull request #16 from Manuel83/master
August 2 update
2018-08-03 08:40:43 -07:00
Manuel83andGitHub 1414b1ed3b Merge pull request #166 from DrRataplan/master
Import hop alerts from BeerXML
2018-08-02 12:54:14 +02:00
Manuel83andGitHub acd404622b Merge pull request #182 from archonic/patch-1
Remove duplicate line
2018-05-23 10:19:17 +02:00
Manuel83andGitHub d657077570 Merge pull request #181 from clearwaterbrewer/patch-1
Fix hysteresis turning off heater
2018-05-23 10:18:41 +02:00
JoshandGitHub 0e6da89cfd Remove duplicate line 2018-04-05 08:53:19 -07:00
clearwaterbrewerandGitHub faa2b9d32c Update __init__.py
removed lines 37 and 38 to make hysteresis work properly.
2018-04-05 10:49:35 -04:00
Martin 83699f1933 Import boil time alerts from BeerXML
By reading the HOP and MISC elements, the 'please add your hop' alerts may be imported.
2018-01-07 11:45:15 +00:00
swimIanandGitHub fea3e85053 Merge pull request #15 from Manuel83/master
Update swimIan master
2017-11-15 09:31:46 -08:00
Manuel83andGitHub 5efa1df350 Merge pull request #133 from JamFfm/JamFfm-install
Added a combinaton of stop-del logfiles-start
2017-11-08 19:45:08 +01:00
Manuel83andGitHub dd5b5f456a Merge pull request #151 from mb-tec/master
Buzzer Beep Level
2017-11-08 19:44:32 +01:00
Matthias e0e3aff929 Buzzer Beep Level 2017-10-28 22:08:30 +02:00
Manuel83andGitHub 2a02979e37 Merge pull request #142 from Manuel83/revert-105-patch2
Revert "Updates the boilstep and beerxml recipe import"
2017-10-15 14:48:45 +02:00
Manuel83andGitHub a1740074ac Revert "Updates the boilstep and beerxml recipe import" 2017-10-15 14:48:24 +02:00
swimIanandGitHub 1b3042a082 Merge pull request #13 from Manuel83/master
Update
2017-10-14 07:55:06 -07:00
Manuel83andGitHub b279dcf347 Merge pull request #105 from swimIan/patch2
Updates the boilstep and beerxml recipe import
2017-10-13 21:56:17 +02:00
JamFfmandGitHub 211edc1bbc Added a combinaton of stop-del logfiles-start
This one makes it easier to test plugins.
2017-10-01 14:11:52 +02:00
swimIanandGitHub b56c10a8a5 Merge branch 'master' into patch2 2017-09-29 13:43:51 -07:00
swimIan 8d2c5c916a Updated again 2017-09-29 13:40:55 -07:00
swimIan d637c08eba update to reflect changes in the master branch 2017-09-29 13:12:03 -07:00
swimIanandGitHub 0447621895 Merge pull request #10 from Manuel83/master
Most recent
2017-09-28 21:44:17 -07:00
Manuel83andGitHub 03395647ed Merge pull request #130 from bertah/master
GPIOPWM: Fixed misleading label and support decimal frequencies
2017-09-25 13:22:46 +02:00
bertah 65833b1249 GPIOPWM: Fixed misleading label "duty cycle" to "frequency", and handle decimal frequency values. Tested working with Rpi.GPIO 2017-09-23 10:03:48 -04:00
Manuel83andGitHub 25ace8024c Merge pull request #127 from mrillies/patch-1
Add reset to post put call (Fermenter update)
2017-09-22 13:55:10 +02:00
Manuel83 820061cf09 - Two new hop steps added. Now you can set 5 hop alerts 2017-09-22 13:52:00 +02:00
mrilliesandGitHub c1c5faa1f3 Add reset to post put call (Fermenter update)
Added a call to reset the fermenter after an update.

Reset builds step parameter with FerementerStep objects from database.
Reset all clears current ferment steps - this may be positive or negative
2017-09-22 21:41:19 +10:00
Manuel83andGitHub 837b906af1 Merge pull request #126 from mrillies/patch-1
Fix Fermentor Heater power
2017-09-20 23:41:43 +02:00
mrilliesandGitHub 0f42153591 Update controller.py
Fix for issue #125: Missing 'power' in call to actor_on had heater id number set as power
2017-09-21 02:14:56 +10:00
swimIan 5fc5684e56 update boilstep and reorder steps in beerxml recipe import 2017-09-09 10:51:37 -07:00
swimIanandGitHub dc908268dd Merge pull request #9 from swimIan/master
update 9/6/17
2017-09-06 06:10:43 -07:00
swimIanandGitHub 14e4e6d61a Merge pull request #8 from Manuel83/master
Update 9/6/17
2017-09-06 06:08:22 -07:00
swimIan ae7e3a2e7b Adjust check_hop_timer function to alert the user to add the hops at the expected values in the boil process (ex. 60= bittering, 20=aroma, etc), instead of the reverse (ex. 0=bittering, 40=aroma, etc). 2017-08-08 23:36:18 -07:00
swimIan 3eee952a04 Add mashinstep, import mashin temp(infuse_temp) from beerxml, and put boil step ahead of whirlpool and chill steps. 2017-08-08 22:48:23 -07:00
swimIanandGitHub 88b360a8d7 Merge pull request #7 from Manuel83/master
- Add On - Link to Plugin Website
2017-07-29 07:13:20 -07:00
swimIanandGitHub 3761713219 Merge pull request #6 from swimIan/patch2
Merge pull request #5 from swimIan/master
2017-07-22 07:55:19 -07:00
swimIanandGitHub a96c678dd9 Merge pull request #5 from swimIan/master
Update
2017-07-22 07:53:16 -07:00
swimIanandGitHub e7765a2c4c Merge pull request #4 from Manuel83/master
Update
2017-07-22 07:51:27 -07:00
swimIanandGitHub 74b56937e7 Merge pull request #3 from Manuel83/master
Update
2017-07-18 21:43:34 -07:00
78 changed files with 3305 additions and 3647 deletions
+14
View File
@@ -0,0 +1,14 @@
# Dockerfile for development on a pc/mac
FROM python:2
EXPOSE 5000
WORKDIR /usr/src/app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "run.py"]
+15
View File
@@ -28,6 +28,21 @@ http://web.craftbeerpi.com/hardware/
CraftBeerPi 3.0 is a complete rewrite. Server as well as user interface. I recommend to use a second SD card for testing. CraftBeerPi 3.0 is a complete rewrite. Server as well as user interface. I recommend to use a second SD card for testing.
## Docker-based development
For developing this application or its plugins on a PC/Mac you can use the docker-compose file:
``` shell
$ docker-compose up
...
Starting craftbeerpi3_app_1 ... done
Attaching to craftbeerpi3_app_1
app_1 | [2018-08-13 12:54:44,264] ERROR in __init__: BUZZER not working
app_1 | (1) wsgi starting up on http://0.0.0.0:5000
```
The contents of this folder will be mounted to `/usr/src/craftbeerpi3` and the server will be accesible on `localhost:5000`.
## Donation ## Donation
CraftBeerPi is a free & open source project. If you like to support the project I happy about a donation: CraftBeerPi is a free & open source project. If you like to support the project I happy about a donation:
+2 -1
View File
@@ -84,7 +84,8 @@ INSERT OR IGNORE INTO config VALUES ('actor_cols', 4, 'select', 'Adjust the widt
INSERT OR IGNORE INTO config VALUES ('sensor_cols', 4, 'select', 'Adjust the width of a sensor widget on the brewing dashboard', '[1,2,3, 4, 5, 6, 7, 8, 9, 10, 11, 12]'); INSERT OR IGNORE INTO config VALUES ('sensor_cols', 4, 'select', 'Adjust the width of a sensor widget on the brewing dashboard', '[1,2,3, 4, 5, 6, 7, 8, 9, 10, 11, 12]');
INSERT OR IGNORE INTO config VALUES ('unit', 'C', 'select', 'Temperature Unit', '["C","F"]'); INSERT OR IGNORE INTO config VALUES ('unit', 'C', 'select', 'Temperature Unit', '["C","F"]');
INSERT OR IGNORE INTO config VALUES ('brewery_name', 'My Home Brewery', 'text', 'Your brewery name', NULL ); INSERT OR IGNORE INTO config VALUES ('brewery_name', 'My Home Brewery', 'text', 'Your brewery name', NULL );
INSERT OR IGNORE INTO config VALUES ('buzzer', 16, 'select', 'Buzzer GPIO', '[16,17,18,19,20]'); INSERT OR IGNORE INTO config VALUES ('buzzer', 16, 'select', 'Buzzer GPIO', '[16,17,18,19,20,21,22,23]');
INSERT OR IGNORE INTO config VALUES ('buzzer_beep_level', 'HIGH', 'select', 'Buzzer Logic Beep Level', '["HIGH", "LOW"]');
INSERT OR IGNORE INTO config VALUES ('setup', 'YES', 'select', 'Show the Setup dialog', '["YES","NO"]'); INSERT OR IGNORE INTO config VALUES ('setup', 'YES', 'select', 'Show the Setup dialog', '["YES","NO"]');
INSERT OR IGNORE INTO config VALUES ('brew_name', '', 'text', 'Brew Name', NULL); INSERT OR IGNORE INTO config VALUES ('brew_name', '', 'text', 'Brew Name', NULL);
INSERT OR IGNORE INTO config VALUES ('donation_notification', 'YES', 'select', 'Disable Donation Notification', '["YES","NO"]'); INSERT OR IGNORE INTO config VALUES ('donation_notification', 'YES', 'select', 'Disable Donation Notification', '["YES","NO"]');
+9
View File
@@ -0,0 +1,9 @@
# docker-compose file for development on a pc/mac
version: '3.3'
services:
app:
build: .
ports:
- 5000:5000
volumes:
- $PWD:/usr/src/app
-27
View File
@@ -1,27 +0,0 @@
import requests
from git import Repo, Git
repo = Repo('./')
branch = repo.active_branch
print branch.name
for remote in repo.remotes:
remote.fetch()
url = 'https://api.github.com/repos/manuel83/craftbeerpi3/releases'
response = requests.get(url)
result = {"branches":[], "releases": []}
result["branches"].append({"name": "master"})
for branch in repo.branches:
result["branches"].append({"name": branch.name})
for r in response.json():
result["releases"].append({"name": "tags/%s" % r.get("tag_name")})
print result
+13 -9
View File
@@ -19,7 +19,8 @@ show_menu () {
"7" "Software Update (git pull)" \ "7" "Software Update (git pull)" \
"8" "Reset File Changes (git reset --hard)" \ "8" "Reset File Changes (git reset --hard)" \
"9" "Clear all logs" \ "9" "Clear all logs" \
"10" "Reboot Raspberry Pi" 3>&1 1>&2 2>&3) "10" "Reboot Raspberry Pi" \
"11" "Stop CraftBeerPi, Clear logs, Start CraftBeerPi" 3>&1 1>&2 2>&3)
BUTTON=$? BUTTON=$?
# Exit if user pressed cancel or escape # Exit if user pressed cancel or escape
@@ -34,14 +35,6 @@ show_menu () {
apt-get -y update; apt-get -y upgrade; apt-get -y update; apt-get -y upgrade;
fi fi
confirmAnswer "Would you like to install wiringPI? This is required to control the GPIO"
if [ $? = 0 ]; then
git clone git://git.drogon.net/wiringPi;
cd wiringPi;
./build; cd ..;
rm -rf wiringPi;
fi
apt-get -y install python-setuptools apt-get -y install python-setuptools
easy_install pip easy_install pip
apt-get -y install python-dev apt-get -y install python-dev
@@ -143,6 +136,17 @@ show_menu () {
show_menu show_menu
fi fi
;; ;;
11)
confirmAnswer "Are you sure you want to reboot CraftBeerPi and delete all log files?"
if [ $? = 0 ]; then
sudo /etc/init.d/craftbeerpiboot stop
sudo rm -rf logs/*.log
sudo /etc/init.d/craftbeerpiboot start
show_menu
else
show_menu
fi
;;
esac esac
fi fi
} }
+72
View File
@@ -0,0 +1,72 @@
import json
import pprint
import sys, os
from flask import Flask, render_template, redirect
from flask_socketio import SocketIO, emit
import logging
# Define the WSGI application object
from app_config import *
import pprint
from modules.core.db import get_db
@app.route('/')
def index():
return redirect('ui')
# Define the database object which is imported
# by modules and controllers
import modules.steps
import modules.config
import modules.logs
import modules.sensors
import modules.actor
import modules.notification
import modules.fermenter
from modules.addon.endpoints import initPlugins
import modules.ui
import modules.system
import modules.buzzer
import modules.stats
import modules.kettle
import modules.recipe_import
import modules.core.db_mirgrate
from app_config import cbpi
# Build the database:
# This will create the database file using SQLAlchemy
pp = pprint.PrettyPrinter(indent=6)
def init_db():
print "INIT DB"
with app.app_context():
db = get_db()
try:
with app.open_resource('../config/schema.sql', mode='r') as f:
db.cursor().executescript(f.read())
db.commit()
except Exception as e:
pass
init_db()
initPlugins()
cbpi.run_init()
cbpi.run_background_processes()
app.logger.info("##########################################")
app.logger.info("### STARTUP COMPLETE")
app.logger.info("##########################################")
-31
View File
@@ -1,31 +0,0 @@
import json
from flask_classy import FlaskView, route
from modules.core.core import cbpi
class ActionView(FlaskView):
@route('/<action>', methods=['POST'])
def action(self, action):
"""
Call global action button
---
tags:
- action
responses:
200:
description: action invoked
"""
self.cbpi.cache["actions"][action]["function"](self.cbpi)
return ('',204)
@cbpi.addon.core.initializer()
def init(cbpi):
"""
Initializer for the message module
:param app: the flask app
:return: None
"""
ActionView.cbpi = cbpi
ActionView.register(cbpi._app, route_base='/api/action')
Executable → Regular
+74 -191
View File
@@ -1,191 +1,74 @@
import time import time
from flask_classy import route from flask_classy import route
from flask_login import login_required from modules import DBModel, cbpi
from modules.core.baseview import BaseView
from modules.core.db import DBModel
from modules.core.core import cbpi class Actor(DBModel):
from modules.core.baseview import BaseView __fields__ = ["name","type", "config", "hide"]
from modules.database.dbmodel import Actor __table_name__ = "actor"
__json_fields__ = ["config"]
class ActorView(BaseView): class ActorView(BaseView):
model = Actor model = Actor
cache_key = "actors" cache_key = "actors"
@classmethod @classmethod
def post_init_callback(self, obj): def post_init_callback(self, obj):
obj.state = 0
obj.state = 0 obj.power = 100
obj.power = 100
def _post_post_callback(self, m):
def _post_post_callback(self, m): self.api.init_actor(m.id)
self.api.actor.init_one(m.id)
def _post_put_callback(self, m):
def _post_put_callback(self, m):
self.api.actor.init_one(m.id) self.api.init_actor(m.id)
@login_required @route("<int:id>/switch/on", methods=["POST"])
@route("<int:id>/switch/on", methods=["POST"]) def on(self, id):
def on(self, id): self.api.switch_actor_on(id)
""" return ('', 204)
Switch actor on
--- @route("<int:id>/switch/off", methods=["POST"])
tags: def off(self, id):
- actor self.api.switch_actor_off(id)
parameters: return ('', 204)
- in: path
name: id @route("<int:id>/power/<int:power>", methods=["POST"])
schema: def power(self, id, power):
type: integer self.api.actor_power(id, power)
required: true return ('', 204)
description: Numeric ID of the actor
responses: @route("<int:id>/toggle", methods=["POST"])
200: def toggle(self, id):
description: Actor switched on
""" if self.api.cache.get("actors").get(id).state == 0:
self.api.actor.on(id) self.on(id)
return ('', 204) else:
self.off(id)
@login_required return ('', 204)
@route("<int:id>/switch/off", methods=["POST"])
def off(self, id): def toggleTimeJob(self, id, t):
""" self.api.cache.get("actors").get(int(id)).timer = int(time.time()) + int(t)
Switch actor off self.toggle(int(id))
--- self.api.socketio.sleep(t)
tags: self.api.cache.get("actors").get(int(id)).timer = None
- actor self.toggle(int(id))
parameters:
- in: path @route("/<id>/toggle/<int:t>", methods=["POST"])
name: id def toggleTime(self, id, t):
schema: t = self.api.socketio.start_background_task(target=self.toggleTimeJob, id=id, t=t)
type: integer return ('', 204)
required: true
description: Numeric ID of the actor @route('<int:id>/action/<method>', methods=["POST"])
responses: def action(self, id, method):
200:
description: Actor switched off cbpi.cache.get("actors").get(id).instance.__getattribute__(method)()
""" return ('', 204)
self.api.actor.off(id)
return ('', 204)
@cbpi.initalizer(order=1000)
@login_required def init(cbpi):
@route("<int:id>/power/<int:power>", methods=["POST"]) ActorView.register(cbpi.app, route_base='/api/actor')
def power(self, id, power): ActorView.init_cache()
""" cbpi.init_actors()
Set Actor Power
---
tags:
- actor
parameters:
- in: path
name: id
schema:
type: integer
required: true
description: Numeric ID of the actor
- in: path
name: power
schema:
type: integer
required: true
description: Power value between 0 - 100
responses:
200:
description: Actor power set
"""
self.api.actor.power(id, power)
return ('', 204)
@login_required
@route("<int:id>/toggle", methods=["POST"])
def toggle(self, id):
"""
Toggle Actor
---
tags:
- actor
parameters:
- in: path
name: id
schema:
type: integer
required: true
description: Numeric ID of the actor
responses:
200:
description: Actor toggled
"""
cbpi.actor.toggle(id)
return ('', 204)
def toggleTimeJob(self, id, t):
self.api.cache.get("actors").get(int(id)).timer = int(time.time()) + int(t)
self.toggle(int(id))
self.api._socketio.sleep(t)
self.api.cache.get("actors").get(int(id)).timer = None
self.toggle(int(id))
@login_required
@route("/<id>/toggle/<int:t>", methods=["POST"])
def toggleTime(self, id, t):
"""
Toggle Actor for a defined time
---
tags:
- actor
parameters:
- in: path
name: id
schema:
type: integer
required: true
description: Numeric ID of the actor
- in: path
name: time
schema:
type: integer
required: true
description: time in seconds
responses:
200:
description: Actor toggled
"""
self.api.actor.toggle_timeout(id, t)
#t = self.api._socketio.start_background_task(target=self.toggleTimeJob, id=id, t=t)
return ('', 204)
@login_required
@route('<int:id>/action/<method>', methods=["POST"])
def action(self, id, method):
"""
Actor Action
---
tags:
- actor
parameters:
- in: path
name: id
schema:
type: integer
required: true
description: Numeric ID of the actor
- in: path
name: method
schema:
type: string
required: true
description: action method name
responses:
200:
description: Actor Action called
"""
self.api.actor.action(id, method)
return ('', 204)
@cbpi.addon.core.initializer(order=1000)
def init(cbpi):
ActorView.register(cbpi._app, route_base='/api/actor')
ActorView.init_cache()
#cbpi.init_actors()
+1
View File
@@ -0,0 +1 @@
import endpoints
+199
View File
@@ -0,0 +1,199 @@
import json
import sys
from flask import Blueprint, request, send_from_directory
from importlib import import_module
from modules import socketio, cbpi
from git import Repo
import os
import requests
import yaml
import shutil
blueprint = Blueprint('addon', __name__)
modules = {}
def merge(source, destination):
"""
Helper method to merge two dicts
:param source:
:param destination:
:return:
"""
for key, value in source.items():
if isinstance(value, dict):
# get node or create one
node = destination.setdefault(key, {})
merge(value, node)
else:
destination[key] = value
return destination
@blueprint.route('/', methods=['GET'])
def getPlugins():
"""
Endpoint for all plugins
:return:
"""
result = []
for filename in os.listdir("./modules/plugins"):
if filename.endswith(".DS_Store") or filename.endswith(".py") or filename.endswith(".pyc"):
continue
result.append(filename)
return json.dumps(result)
@blueprint.route('/<name>', methods=['GET'])
def getFile(name):
"""
Returns plugin code
:param name: plugin name
:return: the plugin code from __init__.py
"""
return send_from_directory('./plugins/'+name, "__init__.py")
@blueprint.route('/<name>', methods=['PUT'])
def createPlugin(name):
"""
Create a new plugin file
:param name: the plugin name
:return: empty http response 204
"""
if not os.path.exists("./modules/plugins/"+name):
os.makedirs("./modules/plugins/"+name)
with open("./modules/plugins/" + name + "/__init__.py", "wb") as fo:
fo.write("")
cbpi.emit_message("PLUGIN %s CREATED" % (name))
return ('', 204)
else:
cbpi.emit_message("Failed to create plugin %s. Name arlready in use" % (name))
return ('', 500)
@blueprint.route('/<name>', methods=['POST'])
def saveFile(name):
"""
save plugin code. code is provides via http body
:param name: the plugin name
:return: empty http reponse
"""
with open("./modules/plugins/"+name+"/__init__.py", "wb") as fo:
fo.write(request.get_data())
cbpi.emit_message("PLUGIN %s SAVED" % (name))
return ('', 204)
@blueprint.route('/<name>', methods=['DELETE'])
def deletePlugin(name):
"""
Delete plugin
:param name: plugin name
:return: HTTP 204 if ok - HTTP 500 if plugin not exists
"""
if os.path.isdir("./modules/plugins/"+name) is False:
return ('Dir Not found', 500)
shutil.rmtree("./modules/plugins/"+name)
cbpi.notify("Plugin deleted", "Plugin %s deleted successfully" % name)
return ('', 204)
@blueprint.route('/<name>/reload/', methods=['POST'])
def reload(name):
"""
hot reload plugnin
:param name:
:return:
"""
try:
if name in cache["modules"]:
reload(cache["modules"][name])
cbpi.emit_message("REALOD OF PLUGIN %s SUCCESSFUL" % (name))
return ('', 204)
else:
cache["modules"][name] = import_module("modules.plugins.%s" % (name))
return ('', 204)
except Exception as e:
cbpi.emit_message("REALOD OF PLUGIN %s FAILED" % (name))
return json.dumps(e.message)
@blueprint.route('/list', methods=['GET'])
def plugins():
"""
Read the central plugin yaml to get a list of all official plugins
:return:
"""
response = requests.get("https://raw.githubusercontent.com/Manuel83/craftbeerpi-plugins/master/plugins.yaml")
cbpi.cache["plugins"] = merge(yaml.load(response.text), cbpi.cache["plugins"])
for key, value in cbpi.cache["plugins"].iteritems():
value["installed"] = os.path.isdir("./modules/plugins/%s/" % (key))
return json.dumps(cbpi.cache["plugins"])
@blueprint.route('/<name>/download', methods=['POST'])
def download_addon(name):
plugin = cbpi.cache["plugins"].get(name)
plugin["loading"] = True
if plugin is None:
return ('', 404)
try:
Repo.clone_from(plugin.get("repo_url"), "./modules/plugins/%s/" % (name))
cbpi.notify("Download successful", "Plugin %s downloaded successfully" % name)
finally:
plugin["loading"] = False
return ('', 204)
@blueprint.route('/<name>/update', methods=['POST'])
def update_addon(name):
repo = Repo("./modules/plugins/%s/" % (name))
o = repo.remotes.origin
info = o.pull()
cbpi.notify("Plugin Updated", "Plugin %s updated successfully. Please restart the system" % name)
return ('', 204)
def loadCorePlugins():
for filename in os.listdir("./modules/base_plugins"):
if os.path.isdir("./modules/base_plugins/"+filename) is False:
continue
try:
modules[filename] = import_module("modules.base_plugins.%s" % (filename))
except Exception as e:
cbpi.notify("Failed to load plugin %s " % filename, str(e), type="danger", timeout=None)
cbpi.app.logger.error(e)
def loadPlugins():
for filename in os.listdir("./modules/plugins"):
if os.path.isdir("./modules/plugins/" + filename) is False:
continue
try:
modules[filename] = import_module("modules.plugins.%s" % (filename))
except Exception as e:
cbpi.notify("Failed to load plugin %s " % filename, str(e), type="danger", timeout=None)
cbpi.app.logger.error(e)
#@cbpi.initalizer(order=1)
def initPlugins():
loadCorePlugins()
loadPlugins()
@cbpi.initalizer(order=2)
def init(cbpi):
cbpi.app.register_blueprint(blueprint, url_prefix='/api/editor')
+55
View File
@@ -0,0 +1,55 @@
import json
import sys, os
from flask import Flask, render_template, redirect, json, g
from flask_socketio import SocketIO, emit
import logging
from modules.core.core import CraftBeerPi, ActorBase, SensorBase
from modules.core.db import DBModel
app = Flask(__name__)
FORMAT = '%(asctime)-15s - %(levelname)s - %(message)s'
logging.basicConfig(filename='./logs/app.log',level=logging.INFO, format=FORMAT)
app.config['SECRET_KEY'] = 'craftbeerpi'
app.config['UPLOAD_FOLDER'] = './upload'
@app.teardown_appcontext
def close_connection(exception):
db = getattr(g, '_database', None)
if db is not None:
db.close()
class ComplexEncoder(json.JSONEncoder):
def default(self, obj):
try:
if isinstance(obj, DBModel):
return obj.__dict__
elif isinstance(obj, ActorBase):
return obj.state()
elif isinstance(obj, SensorBase):
return obj.get_value()
elif hasattr(obj, "callback"):
return obj()
else:
return None
except TypeError as e:
pass
return None
app.json_encoder = ComplexEncoder
socketio = SocketIO(app, json=json, logging=False)
cbpi = CraftBeerPi(app, socketio)
app.logger.info("##########################################")
app.logger.info("### NEW STARTUP Version 3.0")
app.logger.info("##########################################")
-50
View File
@@ -1,50 +0,0 @@
from modules.core.baseapi import Buzzer
from modules.core.basetypes import Actor, KettleController, FermenterController
from modules.core.core import cbpi
@cbpi.addon.actor.type("Dummy Actor")
class Dummy(Actor):
@cbpi.addon.actor.action("WOHOO")
def myaction(self):
pass
def on(self, power=100):
'''
Code to switch on the actor
:param power: int value between 0 - 100
:return:
'''
print "ON"
def off(self):
print "OFF"
@cbpi.addon.kettle.controller()
class MyController(KettleController):
def run(self):
while self.is_running():
self.sleep(1)
@cbpi.addon.fermenter.controller()
class MyController2(FermenterController):
def run(self):
while self.is_running():
print "HALLO"
self.sleep(1)
@cbpi.addon.core.initializer(order=200)
def init(cbpi):
class MyBuzzer(Buzzer):
def beep(self):
pass
cbpi.buzzer = MyBuzzer()
+229
View File
@@ -0,0 +1,229 @@
# -*- coding: utf-8 -*-
import time
from modules.core.props import Property, StepProperty
from modules.core.step import StepBase
from modules import cbpi
@cbpi.step
class MashStep(StepBase):
'''
Just put the decorator @cbpi.step on top of a method
'''
# Properties
temp = Property.Number("Temperature", configurable=True, description="Target Temperature of Mash Step")
kettle = StepProperty.Kettle("Kettle", description="Kettle in which the mashing takes place")
timer = Property.Number("Timer in Minutes", configurable=True, description="Timer is started when the target temperature is reached")
def init(self):
'''
Initialize Step. This method is called once at the beginning of the step
:return:
'''
# set target tep
self.set_target_temp(self.temp, self.kettle)
@cbpi.action("Start Timer Now")
def start(self):
'''
Custom Action which can be execute form the brewing dashboard.
All method with decorator @cbpi.action("YOUR CUSTOM NAME") will be available in the user interface
:return:
'''
if self.is_timer_finished() is None:
self.start_timer(int(self.timer) * 60)
def reset(self):
self.stop_timer()
self.set_target_temp(self.temp, self.kettle)
def finish(self):
self.set_target_temp(0, self.kettle)
def execute(self):
'''
This method is execute in an interval
:return:
'''
# Check if Target Temp is reached
if self.get_kettle_temp(self.kettle) >= float(self.temp):
# Check if Timer is Running
if self.is_timer_finished() is None:
self.start_timer(int(self.timer) * 60)
# Check if timer finished and go to next step
if self.is_timer_finished() == True:
self.notify("Mash Step Completed!", "Starting the next step", timeout=None)
self.next()
@cbpi.step
class MashInStep(StepBase):
'''
Just put the decorator @cbpi.step on top of a method
'''
# Properties
temp = Property.Number("Temperature", configurable=True, description="Target Temperature of Mash Step")
kettle = StepProperty.Kettle("Kettle", description="Kettle in which the mashing takes place")
s = False
@cbpi.action("Change Power")
def change_power(self):
self.actor_power(1, 50)
def init(self):
'''
Initialize Step. This method is called once at the beginning of the step
:return:
'''
# set target tep
self.s = False
self.set_target_temp(self.temp, self.kettle)
def execute(self):
'''
This method is execute in an interval
:return:
'''
# Check if Target Temp is reached
if self.get_kettle_temp(self.kettle) >= float(self.temp) and self.s is False:
self.s = True
self.notify("Step Temp Reached!", "Please press the next button to continue", timeout=None)
@cbpi.step
class ChilStep(StepBase):
timer = Property.Number("Timer in Minutes", configurable=True, default_value=0, description="Timer is started immediately")
@cbpi.action("Stat Timer")
def start(self):
if self.is_timer_finished() is None:
self.start_timer(int(self.timer) * 60)
def reset(self):
self.stop_timer()
def finish(self):
pass
def execute(self):
if self.is_timer_finished() is None:
self.start_timer(int(self.timer) * 60)
if self.is_timer_finished() == True:
self.next()
@cbpi.step
class PumpStep(StepBase):
pump = StepProperty.Actor("Pump", description="Pump actor gets toogled")
timer = Property.Number("Timer in Minutes", configurable=True, default_value=0, description="Timer is started immediately")
@cbpi.action("Stat Timer")
def start(self):
if self.is_timer_finished() is None:
self.start_timer(int(self.timer) * 60)
def reset(self):
self.stop_timer()
def finish(self):
self.actor_off(int(self.pump))
def init(self):
self.actor_on(int(self.pump))
def execute(self):
if self.is_timer_finished() is None:
self.start_timer(int(self.timer) * 60)
if self.is_timer_finished() == True:
self.next()
@cbpi.step
class BoilStep(StepBase):
'''
Just put the decorator @cbpi.step on top of a method
'''
# Properties
temp = Property.Number("Temperature", configurable=True, default_value=100, description="Target temperature for boiling")
kettle = StepProperty.Kettle("Kettle", description="Kettle in which the boiling step takes place")
timer = Property.Number("Timer in Minutes", configurable=True, default_value=90, description="Timer is started when target temperature is reached")
hop_1 = Property.Number("Hop 1 Addition", configurable=True, description="Fist Hop alert")
hop_1_added = Property.Number("",default_value=None)
hop_2 = Property.Number("Hop 2 Addition", configurable=True, description="Second Hop alert")
hop_2_added = Property.Number("", default_value=None)
hop_3 = Property.Number("Hop 3 Addition", configurable=True)
hop_3_added = Property.Number("", default_value=None, description="Third Hop alert")
hop_4 = Property.Number("Hop 4 Addition", configurable=True)
hop_4_added = Property.Number("", default_value=None, description="Fourth Hop alert")
hop_5 = Property.Number("Hop 5 Addition", configurable=True)
hop_5_added = Property.Number("", default_value=None, description="Fives Hop alert")
def init(self):
'''
Initialize Step. This method is called once at the beginning of the step
:return:
'''
# set target tep
self.set_target_temp(self.temp, self.kettle)
@cbpi.action("Start Timer Now")
def start(self):
'''
Custom Action which can be execute form the brewing dashboard.
All method with decorator @cbpi.action("YOUR CUSTOM NAME") will be available in the user interface
:return:
'''
if self.is_timer_finished() is None:
self.start_timer(int(self.timer) * 60)
def reset(self):
self.stop_timer()
self.set_target_temp(self.temp, self.kettle)
def finish(self):
self.set_target_temp(0, self.kettle)
def check_hop_timer(self, number, value):
if self.__getattribute__("hop_%s_added" % number) is not True and time.time() > (
self.timer_end - (int(self.timer) * 60 - int(value) * 60)):
self.__setattr__("hop_%s_added" % number, True)
self.notify("Hop Alert", "Please add Hop %s" % number, timeout=None)
def execute(self):
'''
This method is execute in an interval
:return:
'''
# Check if Target Temp is reached
if self.get_kettle_temp(self.kettle) >= float(self.temp):
# Check if Timer is Running
if self.is_timer_finished() is None:
self.start_timer(int(self.timer) * 60)
else:
self.check_hop_timer(1, self.hop_1)
self.check_hop_timer(2, self.hop_2)
self.check_hop_timer(3, self.hop_3)
self.check_hop_timer(4, self.hop_4)
self.check_hop_timer(5, self.hop_5)
# Check if timer finished and go to next step
if self.is_timer_finished() == True:
self.notify("Boil Step Completed!", "Starting the next step", timeout=None)
self.next()
@@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
import subprocess
import time
from modules import cbpi, socketio
from modules.core.hardware import SensorActive
from modules import cbpi
from modules.core.props import Property
@cbpi.sensor
class DummyTempSensor(SensorActive):
temp = Property.Number("Temperature", configurable=True, default_value=5, description="Dummy Temperature as decimal value")
@cbpi.action("My Custom Action")
def my_action(self):
print "HELLO WORLD"
pass
def get_unit(self):
'''
:return: Unit of the sensor as string. Should not be longer than 3 characters
'''
return "°C" if self.get_config_parameter("unit", "C") == "C" else "°F"
def stop(self):
SensorActive.stop(self)
def execute(self):
'''
Active sensor has to handle his own loop
:return:
'''
while self.is_running() is True:
self.data_received(self.temp)
self.sleep(5)
@classmethod
def init_global(cls):
'''
Called one at the startup for all sensors
:return:
'''
@@ -0,0 +1,38 @@
from modules import cbpi
from modules.core.controller import KettleController, FermenterController
from modules.core.props import Property
@cbpi.fermentation_controller
class Hysteresis(FermenterController):
heater_offset_min = Property.Number("Heater Offset ON", True, 0, description="Offset as decimal number when the heater is switched on. Should be greater then 'Heater Offset OFF'. For example a value of 2 switches on the heater if the current temperature is 2 degrees below the target temperature")
heater_offset_max = Property.Number("Heater Offset OFF", True, 0, description="Offset as decimal number when the heater is switched off. Should be smaller then 'Heater Offset ON'. For example a value of 1 switches off the heater if the current temperature is 1 degree below the target temperature")
cooler_offset_min = Property.Number("Cooler Offset ON", True, 0, description="Offset as decimal number when the cooler is switched on. Should be greater then 'Cooler Offset OFF'. For example a value of 2 switches on the cooler if the current temperature is 2 degrees above the target temperature")
cooler_offset_max = Property.Number("Cooler Offset OFF", True, 0, description="Offset as decimal number when the cooler is switched off. Should be less then 'Cooler Offset ON'. For example a value of 1 switches off the cooler if the current temperature is 1 degree above the target temperature")
def stop(self):
super(FermenterController, self).stop()
self.heater_off()
self.cooler_off()
def run(self):
while self.is_running():
target_temp = self.get_target_temp()
temp = self.get_temp()
if temp + float(self.heater_offset_min) <= target_temp:
self.heater_on(100)
if temp + float(self.heater_offset_max) >= target_temp:
self.heater_off()
if temp >= target_temp + float(self.cooler_offset_min):
self.cooler_on(100)
if temp <= target_temp + float(self.cooler_offset_max):
self.cooler_off()
self.sleep(1)
+107
View File
@@ -0,0 +1,107 @@
# -*- coding: utf-8 -*-
import time
from modules import cbpi
from modules.core.hardware import ActorBase, SensorPassive, SensorActive
from modules.core.props import Property
try:
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
except Exception as e:
print e
pass
@cbpi.actor
class GPIOSimple(ActorBase):
gpio = Property.Select("GPIO", options=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27], description="GPIO to which the actor is connected")
def init(self):
GPIO.setup(int(self.gpio), GPIO.OUT)
GPIO.output(int(self.gpio), 0)
def on(self, power=0):
print "GPIO ON %s" % str(self.gpio)
GPIO.output(int(self.gpio), 1)
def off(self):
print "GPIO OFF"
GPIO.output(int(self.gpio), 0)
@cbpi.actor
class GPIOPWM(ActorBase):
gpio = Property.Select("GPIO", options=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27], description="GPIO to which the actor is connected")
frequency = Property.Number("Frequency (Hz)", configurable=True)
p = None
power = 100 # duty cycle
def init(self):
GPIO.setup(int(self.gpio), GPIO.OUT)
GPIO.output(int(self.gpio), 0)
def on(self, power=None):
if power is not None:
self.power = int(power)
if self.frequency is None:
self.frequency = 0.5 # 2 sec
self.p = GPIO.PWM(int(self.gpio), float(self.frequency))
self.p.start(int(self.power))
def set_power(self, power):
'''
Optional: Set the power of your actor
:param power: int value between 0 - 100
:return:
'''
if power is not None:
self.power = int(power)
self.p.ChangeDutyCycle(self.power)
def off(self):
print "GPIO OFF"
self.p.stop()
@cbpi.actor
class RelayBoard(ActorBase):
gpio = Property.Select("GPIO", options=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27], description="GPIO to which the actor is connected")
def init(self):
GPIO.setup(int(self.gpio), GPIO.OUT)
GPIO.output(int(self.gpio), 1)
def on(self, power=0):
GPIO.output(int(self.gpio), 0)
def off(self):
GPIO.output(int(self.gpio), 1)
@cbpi.actor
class Dummy(ActorBase):
def on(self, power=100):
'''
Code to switch on the actor
:param power: int value between 0 - 100
:return:
'''
print "ON"
def off(self):
print "OFF"
@@ -0,0 +1,38 @@
from modules import cbpi
from modules.core.controller import KettleController
from modules.core.props import Property
@cbpi.controller
class Hysteresis(KettleController):
# Custom Properties
on = Property.Number("Offset On", True, 0, description="Offset below target temp when heater should switched on. Should be bigger then Offset Off")
off = Property.Number("Offset Off", True, 0, description="Offset below target temp when heater should switched off. Should be smaller then Offset Off")
def stop(self):
'''
Invoked when the automatic is stopped.
Normally you switch off the actors and clean up everything
:return: None
'''
super(KettleController, self).stop()
self.heater_off()
def run(self):
'''
Each controller is exectuted in its own thread. The run method is the entry point
:return:
'''
while self.is_running():
if self.get_temp() < self.get_target_temp() - float(self.on):
self.heater_on(100)
elif self.get_temp() >= self.get_target_temp() - float(self.off):
self.heater_off()
self.sleep(1)
+118
View File
@@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-
import os
from subprocess import Popen, PIPE, call
from modules import cbpi, app
from modules.core.hardware import SensorPassive
import json
import os, re, threading, time
from flask import Blueprint, render_template, request
from modules.core.props import Property
blueprint = Blueprint('one_wire', __name__)
temp = 22
def getSensors():
try:
arr = []
for dirname in os.listdir('/sys/bus/w1/devices'):
if (dirname.startswith("28") or dirname.startswith("10")):
cbpi.app.logger.info("Device %s Found (Family: 28/10, Thermometer on GPIO4 (w1))" % dirname)
arr.append(dirname)
return arr
except:
return []
class myThread (threading.Thread):
value = 0
def __init__(self, sensor_name):
threading.Thread.__init__(self)
self.value = 0
self.sensor_name = sensor_name
self.runnig = True
def shutdown(self):
pass
def stop(self):
self.runnig = False
def run(self):
while self.runnig:
try:
app.logger.info("READ TEMP")
## Test Mode
if self.sensor_name is None:
return
with open('/sys/bus/w1/devices/w1_bus_master1/%s/w1_slave' % self.sensor_name, 'r') as content_file:
content = content_file.read()
if (content.split('\n')[0].split(' ')[11] == "YES"):
temp = float(content.split("=")[-1]) / 1000 # temp in Celcius
self.value = temp
except:
pass
time.sleep(4)
@cbpi.sensor
class ONE_WIRE_SENSOR(SensorPassive):
sensor_name = Property.Select("Sensor", getSensors(), description="The OneWire sensor address.")
offset = Property.Number("Offset", True, 0, description="Offset which is added to the received sensor data. Positive and negative values are both allowed.")
def init(self):
self.t = myThread(self.sensor_name)
def shudown():
shudown.cb.shutdown()
shudown.cb = self.t
self.t.start()
def stop(self):
try:
self.t.stop()
except:
pass
def read(self):
if self.get_config_parameter("unit", "C") == "C":
self.data_received(round(self.t.value + self.offset_value(), 2))
else:
self.data_received(round(9.0 / 5.0 * self.t.value + 32 + self.offset_value(), 2))
@cbpi.try_catch(0)
def offset_value(self):
return float(self.offset)
@classmethod
def init_global(self):
try:
call(["modprobe", "w1-gpio"])
call(["modprobe", "w1-therm"])
except Exception as e:
pass
@blueprint.route('/<int:t>', methods=['GET'])
def set_temp(t):
global temp
temp = t
return ('', 204)
@cbpi.initalizer()
def init(cbpi):
cbpi.app.register_blueprint(blueprint, url_prefix='/api/one_wire')
-47
View File
@@ -1,47 +0,0 @@
# -*- coding: utf-8 -*-
import os
from os.path import join
from modules.core.basetypes import Actor, Sensor
from modules.core.core import cbpi
from modules.core.proptypes import Property
import random
@cbpi.addon.sensor.type("Dummy Sensor")
class Dummy(Sensor):
text = Property.Text(label="Text", required=True, description="This is a parameter", configurable=True)
p = Property.Select(label="hallo",options=[1,2,3])
def init(self):
if self.api.get_config_parameter("unit","C") == "C":
self.unit = "°C"
else:
self.unit = "°F"
@cbpi.addon.sensor.action("WOHOO")
def myaction(self):
print "SENSOR ACTION HALLO!!!"
def execute(self):
while True:
try:
self.update_value(int(self.text))
except:
pass
self.api.sleep(1)
@cbpi.addon.core.action(key="clear", label="Clear all Logs")
def woohoo(cbpi):
dir = "./logs"
test = os.listdir(dir)
for item in test:
if item.endswith(".log"):
os.remove(join(dir, item))
cbpi.notify(headline="Logs Deleted",message="All Logs Cleared")
-25
View File
@@ -1,25 +0,0 @@
from modules.core.basetypes import Step
from modules.core.core import cbpi
from modules.core.proptypes import Property
@cbpi.addon.step.type("Dummy Step")
class Dummy(Step):
@cbpi.addon.step.action("WOHOO")
def myaction(self):
self.stop_timer()
self.start_timer(10)
text = Property.Text(label="Text", configurable=True, description="WOHOOO")
time = Property.Text(label="Text", configurable=True, description="WOHOOO")
def execute(self):
#print self.text
pass
def reset(self):
self.stop_timer()
Executable → Regular
+57 -53
View File
@@ -1,53 +1,57 @@
import time import time
from thread import start_new_thread from thread import start_new_thread
from modules import cbpi
from modules.core.baseapi import Buzzer
from modules.core.core import cbpi try:
import RPi.GPIO as GPIO
try: except Exception as e:
import RPi.GPIO as GPIO pass
except Exception as e:
pass class Buzzer(object):
class GPIOBuzzer(Buzzer): sound = ["H", 0.1, "L", 0.1, "H", 0.1, "L", 0.1, "H", 0.1, "L"]
def __init__(self, gpio, beep_level):
sound = ["H", 0.1, "L", 0.1, "H", 0.1, "L", 0.1, "H", 0.1, "L"] try:
cbpi.app.logger.info("INIT BUZZER NOW GPIO%s" % gpio)
self.gpio = int(gpio)
def __init__(self, gpio): self.beep_level = beep_level
try: GPIO.setmode(GPIO.BCM)
cbpi._app.logger.info("INIT BUZZER NOW GPIO%s" % gpio) GPIO.setup(self.gpio, GPIO.OUT)
self.gpio = int(gpio) self.state = True
GPIO.setmode(GPIO.BCM) cbpi.app.logger.info("BUZZER SETUP OK")
GPIO.setup(self.gpio, GPIO.OUT) except Exception as e:
self.state = True cbpi.app.logger.info("BUZZER EXCEPTION %s" % str(e))
cbpi._app.logger.info("BUZZER SETUP OK") self.state = False
except Exception as e:
cbpi._app.logger.info("BUZZER EXCEPTION %s" % str(e)) def beep(self):
self.state = False if self.state is False:
cbpi.app.logger.error("BUZZER not working")
def beep(self): return
if self.state is False:
cbpi._app.logger.error("BUZZER not working") def play(sound):
return try:
for i in sound:
def play(sound): if (isinstance(i, str)):
try: if i == "H" and self.beep_level == "HIGH":
for i in sound: GPIO.output(int(self.gpio), GPIO.HIGH)
if (isinstance(i, str)): elif i == "H" and self.beep_level != "HIGH":
if i == "H": GPIO.output(int(self.gpio), GPIO.LOW)
GPIO.output(int(self.gpio), GPIO.HIGH) elif i == "L" and self.beep_level == "HIGH":
else: GPIO.output(int(self.gpio), GPIO.LOW)
GPIO.output(int(self.gpio), GPIO.LOW) else:
else: GPIO.output(int(self.gpio), GPIO.HIGH)
time.sleep(i) else:
except Exception as e: time.sleep(i)
pass except Exception as e:
pass
start_new_thread(play, (self.sound,))
start_new_thread(play, (self.sound,))
@cbpi.addon.core.initializer(order=1)
def init(cbpi): @cbpi.initalizer(order=1)
gpio = cbpi.get_config_parameter("buzzer", 16) def init(cbpi):
cbpi.buzzer = GPIOBuzzer(gpio) gpio = cbpi.get_config_parameter("buzzer", 22)
beep_level = cbpi.get_config_parameter("buzzer_beep_level", "HIGH")
cbpi.buzzer = Buzzer(gpio, beep_level)
cbpi.beep()
cbpi.app.logger.info("INIT OK")
Executable → Regular
+57 -89
View File
@@ -1,89 +1,57 @@
import time import time
from flask import json, request from flask import json, request
from flask_classy import route from flask_classy import route
from modules.core.core import cbpi from modules import DBModel, cbpi, get_db
from modules.core.db import DBModel from modules.core.baseview import BaseView
from modules.core.baseview import BaseView
from modules.database.dbmodel import Config class Config(DBModel):
__fields__ = ["type", "value", "description", "options"]
__table_name__ = "config"
class ConfigView(BaseView): __json_fields__ = ["options"]
model = Config __priamry_key__ = "name"
cache_key = "config"
@route('/<name>', methods=["PUT"]) class ConfigView(BaseView):
def put(self, name): model = Config
""" cache_key = "config"
Set new config value
--- @route('/<name>', methods=["PUT"])
tags: def put(self, name):
- config
responses: data = request.json
204: data["name"] = name
description: New config value set update_data = {"name": data["name"], "value": data["value"]}
"""
data = request.json if self.api.cache.get(self.cache_key) is not None:
data["name"] = name self.api.cache.get(self.cache_key)[name].__dict__.update(**update_data)
update_data = {"name": data["name"], "value": data["value"]} m = self.model.update(**self.api.cache.get(self.cache_key)[name].__dict__)
self._post_put_callback(self.api.cache.get(self.cache_key)[name])
if self.api.cache.get(self.cache_key) is not None: return json.dumps(self.api.cache.get(self.cache_key)[name].__dict__)
self.api.cache.get(self.cache_key)[name].__dict__.update(**update_data)
m = self.model.update(**self.api.cache.get(self.cache_key)[name].__dict__) @route('/<id>', methods=["GET"])
self._post_put_callback(self.api.cache.get(self.cache_key)[name]) def getOne(self, id):
return ('NOT SUPPORTED', 400)
self.api.emit("CONFIG_UPDATE", name=name, data=data["value"])
return json.dumps(self.api.cache.get(self.cache_key)[name].__dict__) @route('/<id>', methods=["DELETE"])
def delete(self, id):
@route('/<id>', methods=["GET"]) return ('NOT SUPPORTED', 400)
def getOne(self, id):
""" @route('/', methods=["POST"])
Get config parameter def post(self):
--- return ('NOT SUPPORTED', 400)
tags:
- config @classmethod
responses: def init_cache(cls):
400:
description: Get one config parameter via web api is not supported with cls.api.app.app_context():
""" cls.api.cache[cls.cache_key] = {}
return ('NOT SUPPORTED', 400) for key, value in cls.model.get_all().iteritems():
cls.post_init_callback(value)
@route('/<id>', methods=["DELETE"]) cls.api.cache[cls.cache_key][value.name] = value
def delete(self, id):
""" @cbpi.initalizer(order=0)
Delete config parameter def init(cbpi):
---
tags: ConfigView.register(cbpi.app, route_base='/api/config')
- config ConfigView.init_cache()
responses:
400:
description: Deleting config parameter via web api is not supported
"""
return ('NOT SUPPORTED', 400)
@route('/', methods=["POST"])
def post(self):
"""
Get config parameter
---
tags:
- config
responses:
400:
description: Adding new config parameter via web api is not supported
"""
return ('NOT SUPPORTED', 400)
@classmethod
def init_cache(cls):
with cls.api._app.app_context():
cls.api.cache[cls.cache_key] = {}
for key, value in cls.model.get_all().iteritems():
cls.post_init_callback(value)
cls.api.cache[cls.cache_key][value.name] = value
@cbpi.addon.core.initializer(order=0)
def init(cbpi):
ConfigView.register(cbpi._app, route_base='/api/config')
ConfigView.init_cache()
Executable → Regular
View File
-224
View File
@@ -1,224 +0,0 @@
from proptypes import *
class BaseAPI(object):
def __init__(self, cbpi):
self.cbpi = cbpi
self.cbpi.cache[self.key] = {}
def init(self):
for name, value in self.cbpi.cache[self.key].iteritems():
value["class"].init_global()
def parseProps(self, key, cls, **options):
name = cls.__name__
tmpObj = cls()
try:
doc = tmpObj.__doc__.strip()
except:
doc = ""
self.cbpi.cache.get(key)[name] = {"name": name, "class": cls, "description":doc, "properties": [], "actions": []}
self.cbpi.cache.get(key)[name].update(options)
members = [attr for attr in dir(tmpObj) if not callable(getattr(tmpObj, attr)) and not attr.startswith("__")]
for m in members:
t = tmpObj.__getattribute__(m)
if isinstance(t, Property.Number):
self.cbpi.cache.get(key)[name]["properties"].append({"name": m, "label": t.label, "type": "number", "configurable": t.configurable, "description": t.description, "default_value": t.default_value})
elif isinstance(t, Property.Text):
self.cbpi.cache.get(key)[name]["properties"].append({"name": m, "label": t.label, "type": "text", "required": t.required, "configurable": t.configurable, "description": t.description, "default_value": t.default_value})
elif isinstance(tmpObj.__getattribute__(m), Property.Select):
self.cbpi.cache.get(key)[name]["properties"].append({"name": m, "label": t.label, "type": "select", "configurable": True, "options": t.options, "description": t.description})
elif isinstance(tmpObj.__getattribute__(m), Property.Actor):
self.cbpi.cache.get(key)[name]["properties"].append({"name": m, "label": t.label, "type": "actor", "configurable": True, "description": t.description})
elif isinstance(tmpObj.__getattribute__(m), Property.Sensor):
self.cbpi.cache.get(key)[name]["properties"].append({"name": m, "label": t.label, "type": "sensor", "configurable": True, "description": t.description})
elif isinstance(tmpObj.__getattribute__(m), Property.Kettle):
self.cbpi.cache.get(key)[name]["properties"].append({"name": m, "label": t.label, "type": "kettle", "configurable": True, "description": t.description})
for method_name, method in cls.__dict__.iteritems():
if hasattr(method, "action"):
label = method.__getattribute__("label")
self.cbpi.cache.get(key)[name]["actions"].append({"method": method_name, "label": label})
return cls
class SensorAPI(BaseAPI):
key = "sensor_types"
def type(self, description="Step", **options):
def decorator(f):
BaseAPI.parseProps(self, self.key,f, description=description)
return f
return decorator
def action(self, label):
def real_decorator(func):
func.action = True
func.label = label
return func
return real_decorator
class StepAPI(BaseAPI):
key = "step_types"
def init(self):
pass
def type(self, description="Step", **options):
def decorator(f):
BaseAPI.parseProps(self, self.key,f, description=description)
return f
return decorator
def action(self, label):
def real_decorator(func):
func.action = True
func.label = label
return func
return real_decorator
class ActorAPI(BaseAPI):
key = "actor_types"
def type(self, description="", **options):
def decorator(f):
BaseAPI.parseProps(self, self.key, f, description=description)
return f
return decorator
def action(self, label):
def real_decorator(func):
func.action = True
func.label = label
return func
return real_decorator
class KettleAPI(BaseAPI):
key = "controller_types"
def controller(self, description="", **options):
def decorator(f):
BaseAPI.parseProps(self, self.key,f,description=description)
return f
return decorator
def action(self, label):
def real_decorator(func):
func.action = True
func.label = label
return func
return real_decorator
class FermenterAPI(BaseAPI):
key = "fermentation_controller_types"
def controller(self, description="Step", **options):
def decorator(f):
BaseAPI.parseProps(self, self.key,f,description=description)
return f
return decorator
def action(self, label):
def real_decorator(func):
func.action = True
func.label = label
return func
return real_decorator
class CoreAPI(BaseAPI):
key = "core"
def __init__(self, cbpi):
self.cbpi = cbpi
self.cbpi.cache["actions"] = {}
self.cbpi.cache["init"] = []
self.cbpi.cache["js"] = {}
self.cbpi.cache["background"] = []
self.cbpi.cache["web_menu"] =[]
def init(self):
self.cbpi.cache["init"] = sorted(self.cbpi.cache["init"], key=lambda k: k['order'])
for value in self.cbpi.cache.get("init"):
value["function"](self.cbpi)
def job(interval, method):
while True:
try:
method(self.cbpi)
except Exception as e:
print e
self.cbpi._socketio.sleep(interval)
for value in self.cbpi.cache.get("background"):
t = self.cbpi._socketio.start_background_task(target=job, interval=value.get("interval"), method=value.get("function"))
def add_js(self, name, file):
self.cbpi.cache["js"][name] = file
def add_menu_link(self, name, path):
self.cbpi.cache["web_menu"].append(dict(name=name, path=path))
def initializer(self, order=0, **options):
def decorator(f):
self.cbpi.cache.get("init").append({"function": f, "order": order})
return f
return decorator
def action(self, key, label, **options):
def decorator(f):
self.cbpi.cache.get("actions")[key] = {"label": label, "function": f}
return f
return decorator
def backgroundjob(self, key, interval, **options):
def decorator(f):
self.cbpi.cache.get("background").append({"function": f, "key": key, "interval": interval})
return f
return decorator
def listen(self, name, method=None, async=False):
if method is not None:
if self.cbpi.eventbus.get(name) is None:
self.cbpi.eventbus[name] = []
self.cbpi.eventbus[name].append({"function": method, "async": async})
else:
def real_decorator(function):
if self.cbpi.eventbus.get(name) is None:
self.cbpi.eventbus[name] = []
self.cbpi.eventbus[name].append({"function": function, "async": async})
def wrapper(*args, **kwargs):
return function(*args, **kwargs)
return wrapper
return real_decorator
class Buzzer(object):
def beep():
pass
-283
View File
@@ -1,283 +0,0 @@
from modules.core.proptypes import Property
import time
class Base(object):
def __init__(self, *args, **kwds):
for a in kwds:
super(Base, self).__setattr__(a, kwds.get(a))
self.api = kwds.get("cbpi")
self.id = kwds.get("id")
self.value = None
self.__dirty = False
class Actor(Base):
@classmethod
def init_global(cls):
pass
def init(self):
pass
def shutdown(self):
pass
def on(self, power=100):
print "SWITCH ON"
pass
def off(self):
print "SWITCH OFF"
pass
def power(self, power):
print "SET POWER", power
pass
def state(self):
pass
class Sensor(Base):
unit = ""
@classmethod
def init_global(cls):
pass
def init(self):
pass
def get_unit(self):
pass
def stop(self):
pass
def update_value(self, value):
self.value = value
self.cbpi.sensor.write_log(self.id, value)
self.cbpi.emit("SENSOR_UPDATE", id=self.id, value=value)
self.cbpi.ws_emit("SENSOR_UPDATE", self.cbpi.cache["sensors"][self.id])
def execute(self):
print "EXECUTE"
pass
class ControllerBase(object):
__dirty = False
__running = False
@staticmethod
def init_global():
print "GLOBAL CONTROLLER INIT"
def notify(self, headline, message, type="success", timeout=5000):
self.api.notify(headline, message, type, timeout)
def is_running(self):
return self.__running
def init(self):
self.__running = True
def sleep(self, seconds):
self.api.sleep(seconds)
def stop(self):
self.__running = False
def get_sensor_value(self, id):
return self.api.sensor.get_sensor_value(id)
def __init__(self, *args, **kwds):
for a in kwds:
super(ControllerBase, self).__setattr__(a, kwds.get(a))
self.api = kwds.get("api")
self.heater = kwds.get("heater")
self.sensor = kwds.get("sensor")
def actor_on(self,id, power=100):
self.api.actor.on(id, power=power)
def actor_off(self, id):
self.api.actor.off(id)
def actor_power(self, power, id=None):
self.api.actor.power(id, power)
def run(self):
pass
class KettleController(ControllerBase):
@staticmethod
def chart(kettle):
result = []
result.append({"name": "Temp", "data_type": "sensor", "data_id": kettle.sensor})
result.append({"name": "Target Temp", "data_type": "kettle", "data_id": kettle.id})
return result
def __init__(self, *args, **kwds):
ControllerBase.__init__(self, *args, **kwds)
self.kettle_id = kwds.get("kettle_id")
def heater_on(self, power=100):
k = self.api.cache.get("kettle").get(self.kettle_id)
if k.heater is not None:
self.actor_on(k.heater, power)
def heater_off(self):
k = self.api.cache.get("kettle").get(self.kettle_id)
if k.heater is not None:
self.actor_off(k.heater)
def get_temp(self, id=None):
if id is None:
id = self.kettle_id
return self.get_sensor_value(int(self.api.cache.get("kettle").get(id).sensor))
def get_target_temp(self, id=None):
if id is None:
id = self.kettle_id
return self.api.cache.get("kettle").get(id).target_temp
class FermenterController(ControllerBase):
@staticmethod
def chart(fermenter):
result = []
result.append({"name": "Temp", "data_type": "sensor", "data_id": fermenter.sensor})
result.append({"name": "Target Temp", "data_type": "fermenter", "data_id": fermenter.id})
return result
def __init__(self, *args, **kwds):
ControllerBase.__init__(self, *args, **kwds)
self.fermenter_id = kwds.get("fermenter_id")
self.cooler = kwds.get("cooler")
def get_target_temp(self, id=None):
if id is None:
id = self.fermenter_id
return self.api.cache.get("fermenter").get(id).target_temp
def heater_on(self, power=100):
f = self.api.cache.get("fermenter").get(self.fermenter_id)
if f.heater is not None:
self.actor_on(int(f.heater))
def heater_off(self):
f = self.api.cache.get("fermenter").get(self.fermenter_id)
if f.heater is not None:
self.actor_off(int(f.heater))
def cooler_on(self, power=100):
f = self.api.cache.get("fermenter").get(self.fermenter_id)
if f.cooler is not None:
self.actor_on(power, int(f.cooler))
def cooler_off(self):
f = self.api.cache.get("fermenter").get(self.fermenter_id)
if f.cooler is not None:
self.actor_off(int(f.cooler))
def get_temp(self, id=None):
if id is None:
id = self.fermenter_id
return self.get_sensor_value(int(self.api.cache.get("fermenter").get(id).sensor))
class Timer(object):
timer_end = Property.Number("TIMER_END", configurable=False)
def start_timer(self, timer):
if self.timer_end is not None:
return
self.timer_end = int(time.time()) + timer
def stop_timer(self):
if self.timer_end is not None:
self.timer_end = None
def is_timer_running(self):
if self.timer_end is not None:
return True
else:
return False
def timer_remaining(self):
if self.timer_end is not None:
return self.timer_end - int(time.time())
else:
return None
def is_timer_finished(self):
if self.timer_end is None:
return None
if self.timer_end <= int(time.time()):
return True
else:
return False
class Step(Base, Timer):
@classmethod
def init_global(cls):
pass
__dirty = False
managed_fields = []
n = False
def next(self):
self.n = True
def init(self):
pass
def finish(self):
pass
def reset(self):
pass
def execute(self):
print "Step Info"
print "Kettle ID: %s" % self.kettle_id
print "ID: %s" % self.id
def __init__(self, *args, **kwds):
for a in kwds:
super(Step, self).__setattr__(a, kwds.get(a))
self.api = kwds.get("api")
self.id = kwds.get("id")
self.name = kwds.get("name")
self.kettle_id = kwds.get("kettleid")
self.value = None
self.__dirty = False
def is_dirty(self):
return self.__dirty
def reset_dirty(self):
self.__dirty = False
def __setattr__(self, name, value):
if name != "_StepBase__dirty" and name in self.managed_fields:
self.__dirty = True
super(Step, self).__setattr__(name, value)
else:
super(Step, self).__setattr__(name, value)
Executable → Regular
+109 -116
View File
@@ -1,116 +1,109 @@
from flask import request, json from flask import request, json
from flask_classy import route, FlaskView from flask_classy import route, FlaskView
from flask_login import login_required from modules import cbpi
from modules.core.core import cbpi
class BaseView(FlaskView):
class BaseView(FlaskView): as_array = False
cache_key = None
as_array = False api = cbpi
cache_key = None
api = cbpi @route('/<int:id>', methods=["GET"])
def getOne(self, id):
@login_required
@route('/<int:id>', methods=["GET"]) if self.api.cache.get(self.cache_key) is not None:
def getOne(self, id): return json.dumps(self.api.cache.get(self.cache_key).get(id))
if self.api.cache.get(self.cache_key) is not None: else:
return json.dumps(self.api.cache.get(self.cache_key).get(id)) return json.dumps(self.model.get_one(id))
else:
return json.dumps(self.model.get_one(id)) @route('/', methods=["GET"])
def getAll(self):
@login_required if self.api.cache.get(self.cache_key) is not None:
@route('/', methods=["GET"]) return json.dumps(self.api.cache.get(self.cache_key))
def getAll(self): else:
if self.api.cache.get(self.cache_key) is not None: return json.dumps(self.model.get_all())
return json.dumps(self.api.cache.get(self.cache_key))
else: def _pre_post_callback(self, data):
return json.dumps(self.model.get_all()) pass
def _pre_post_callback(self, data):
pass def _post_post_callback(self, m):
pass
def _post_post_callback(self, m): @route('/', methods=["POST"])
pass def post(self):
data = request.json
@login_required self._pre_post_callback(data)
@route('/', methods=["POST"]) m = self.model.insert(**data)
def post(self): if self.api.cache.get(self.cache_key) is not None:
self.api.cache.get(self.cache_key)[m.id] = m
data = request.json
self.api._app.logger.info("INSERT Model %s", self.model.__name__) self._post_post_callback(m)
self._pre_post_callback(data)
m = self.model.insert(**data) return json.dumps(m)
if self.api.cache.get(self.cache_key) is not None:
self.api.cache.get(self.cache_key)[m.id] = m def _pre_put_callback(self, m):
pass
self._post_post_callback(m)
def _post_put_callback(self, m):
return json.dumps(m) pass
def _pre_put_callback(self, m):
pass @route('/<int:id>', methods=["PUT"])
def put(self, id):
def _post_put_callback(self, m): data = request.json
pass data["id"] = id
try:
@login_required del data["instance"]
@route('/<int:id>', methods=["PUT"]) except:
def put(self, id): pass
data = request.json if self.api.cache.get(self.cache_key) is not None:
data["id"] = id self._pre_put_callback(self.api.cache.get(self.cache_key)[id])
try: self.api.cache.get(self.cache_key)[id].__dict__.update(**data)
del data["instance"] m = self.model.update(**self.api.cache.get(self.cache_key)[id].__dict__)
except: self._post_put_callback(self.api.cache.get(self.cache_key)[id])
pass return json.dumps(self.api.cache.get(self.cache_key)[id])
if self.api.cache.get(self.cache_key) is not None: else:
self._pre_put_callback(self.api.cache.get(self.cache_key)[id]) m = self.model.update(**data)
self.api.cache.get(self.cache_key)[id].__dict__.update(**data)
m = self.model.update(**self.api.cache.get(self.cache_key)[id].__dict__) self._post_put_callback(m)
self._post_put_callback(self.api.cache.get(self.cache_key)[id]) return json.dumps(m)
return json.dumps(self.api.cache.get(self.cache_key)[id])
else:
m = self.model.update(**data) def _pre_delete_callback(self, m):
pass
self._post_put_callback(m)
return json.dumps(m) def _post_delete_callback(self, id):
pass
def _pre_delete_callback(self, m): @route('/<int:id>', methods=["DELETE"])
pass def delete(self, id):
if self.api.cache.get(self.cache_key) is not None:
def _post_delete_callback(self, id): self._pre_delete_callback(self.api.cache.get(self.cache_key)[id])
pass del self.api.cache.get(self.cache_key)[id]
m = self.model.delete(id)
@login_required
@route('/<int:id>', methods=["DELETE"]) def _post_delete_callback(self, id):
def delete(self, id): pass
if self.api.cache.get(self.cache_key) is not None: return ('',204)
self._pre_delete_callback(self.api.cache.get(self.cache_key)[id])
del self.api.cache.get(self.cache_key)[id] @classmethod
m = self.model.delete(id) def post_init_callback(cls, obj):
pass
def _post_delete_callback(self, id):
pass @classmethod
return ('',204) def init_cache(cls):
with cls.api.app.app_context():
@classmethod
def post_init_callback(cls, obj): if cls.model.__as_array__ is True:
pass cls.api.cache[cls.cache_key] = []
@classmethod for value in cls.model.get_all():
def init_cache(cls): cls.post_init_callback(value)
with cls.api._app.app_context(): cls.api.cache[cls.cache_key].append(value)
else:
if cls.model.__as_array__ is True: cls.api.cache[cls.cache_key] = {}
cls.api.cache[cls.cache_key] = [] for key, value in cls.model.get_all().iteritems():
cls.post_init_callback(value)
for value in cls.model.get_all(): cls.api.cache[cls.cache_key][key] = value
cls.post_init_callback(value)
cls.api.cache[cls.cache_key].append(value)
else:
cls.api.cache[cls.cache_key] = {}
for key, value in cls.model.get_all().iteritems():
cls.post_init_callback(value)
cls.api.cache[cls.cache_key][key] = value
+168
View File
@@ -0,0 +1,168 @@
from modules import cbpi
class ActorController(object):
@cbpi.try_catch(None)
def actor_on(self, power=100, id=None):
if id is None:
id = self.heater
self.api.switch_actor_on(int(id), power=power)
@cbpi.try_catch(None)
def actor_off(self, id=None):
if id is None:
id = self.heater
self.api.switch_actor_off(int(id))
@cbpi.try_catch(None)
def actor_power(self, power, id=None):
if id is None:
id = self.heater
self.api.actor_power(int(id), power)
class SensorController(object):
@cbpi.try_catch(None)
def get_sensor_value(self, id=None):
if id is None:
id = self.sensor
return cbpi.get_sensor_value(id)
class ControllerBase(object):
__dirty = False
__running = False
@staticmethod
def init_global():
print "GLOBAL CONTROLLER INIT"
def notify(self, headline, message, type="success", timeout=5000):
self.api.notify(headline, message, type, timeout)
def is_running(self):
return self.__running
def init(self):
self.__running = True
def sleep(self, seconds):
self.api.socketio.sleep(seconds)
def stop(self):
self.__running = False
def __init__(self, *args, **kwds):
for a in kwds:
super(ControllerBase, self).__setattr__(a, kwds.get(a))
self.api = kwds.get("api")
self.heater = kwds.get("heater")
self.sensor = kwds.get("sensor")
def run(self):
pass
class KettleController(ControllerBase, ActorController, SensorController):
@staticmethod
def chart(kettle):
result = []
result.append({"name": "Temp", "data_type": "sensor", "data_id": kettle.sensor})
result.append({"name": "Target Temp", "data_type": "kettle", "data_id": kettle.id})
return result
def __init__(self, *args, **kwds):
ControllerBase.__init__(self, *args, **kwds)
self.kettle_id = kwds.get("kettle_id")
@cbpi.try_catch(None)
def heater_on(self, power=100):
k = self.api.cache.get("kettle").get(self.kettle_id)
if k.heater is not None:
self.actor_on(power, int(k.heater))
@cbpi.try_catch(None)
def heater_off(self):
k = self.api.cache.get("kettle").get(self.kettle_id)
if k.heater is not None:
self.actor_off(int(k.heater))
@cbpi.try_catch(None)
def get_temp(self, id=None):
if id is None:
id = self.kettle_id
return self.get_sensor_value(int(self.api.cache.get("kettle").get(id).sensor))
@cbpi.try_catch(None)
def get_target_temp(self, id=None):
if id is None:
id = self.kettle_id
return self.api.cache.get("kettle").get(id).target_temp
class FermenterController(ControllerBase, ActorController, SensorController):
@staticmethod
def chart(fermenter):
result = []
result.append({"name": "Temp", "data_type": "sensor", "data_id": fermenter.sensor})
result.append({"name": "Target Temp", "data_type": "fermenter", "data_id": fermenter.id})
return result
def __init__(self, *args, **kwds):
ControllerBase.__init__(self, *args, **kwds)
self.fermenter_id = kwds.get("fermenter_id")
self.cooler = kwds.get("cooler")
@cbpi.try_catch(None)
def get_target_temp(self, id=None):
if id is None:
id = self.fermenter_id
return self.api.cache.get("fermenter").get(id).target_temp
@cbpi.try_catch(None)
def heater_on(self, power=100):
f = self.api.cache.get("fermenter").get(self.fermenter_id)
if f.heater is not None:
self.actor_on(power, int(f.heater))
@cbpi.try_catch(None)
def heater_off(self):
f = self.api.cache.get("fermenter").get(self.fermenter_id)
if f.heater is not None:
self.actor_off(int(f.heater))
@cbpi.try_catch(None)
def cooler_on(self, power=100):
f = self.api.cache.get("fermenter").get(self.fermenter_id)
if f.cooler is not None:
self.actor_on(power, int(f.cooler))
@cbpi.try_catch(None)
def cooler_off(self):
f = self.api.cache.get("fermenter").get(self.fermenter_id)
if f.cooler is not None:
self.actor_off(int(f.cooler))
@cbpi.try_catch(None)
def get_temp(self, id=None):
if id is None:
id = self.fermenter_id
return self.get_sensor_value(int(self.api.cache.get("fermenter").get(id).sensor))
Executable → Regular
+493 -396
View File
@@ -1,396 +1,493 @@
import json import inspect
import logging import pprint
import os
import sqlite3 import sqlite3
import uuid from flask import make_response, g
from datetime import datetime import datetime
from functools import wraps, update_wrapper from datetime import datetime
from importlib import import_module from flask.views import MethodView
from time import localtime, strftime from flask_classy import FlaskView, route
import time
from time import localtime, strftime
from flask import Flask, redirect, json, g, make_response from functools import wraps, update_wrapper
from flask_socketio import SocketIO
from baseapi import * from props import *
from db import DBModel
from modules.core.basetypes import Sensor, Actor from hardware import *
from modules.database.dbmodel import Kettle
import time
import uuid
class ComplexEncoder(json.JSONEncoder):
def default(self, obj): class NotificationAPI(object):
try: pass
if isinstance(obj, DBModel): class ActorAPI(object):
return obj.__dict__
elif isinstance(obj, Actor): def init_actors(self):
return {"state": obj.value} self.app.logger.info("Init Actors")
elif isinstance(obj, Sensor): t = self.cache.get("actor_types")
return {"value": obj.value, "unit": obj.unit} for key, value in t.iteritems():
elif hasattr(obj, "callback"): value.get("class").api = self
return obj() value.get("class").init_global()
else:
return None for key in self.cache.get("actors"):
self.init_actor(key)
return None
except TypeError as e: def init_actor(self, id):
pass try:
return None value = self.cache.get("actors").get(int(id))
cfg = value.config.copy()
cfg.update(dict(api=self, id=id, name=value.name))
class Addon(object): clazz = self.cache.get("actor_types").get(value.type).get("class")
def __init__(self, cbpi): value.instance = clazz(**cfg)
self.step = StepAPI(cbpi) value.instance.init()
self.actor = ActorAPI(cbpi) value.state = 0
self.sensor = SensorAPI(cbpi) value.power = 100
self.kettle = KettleAPI(cbpi) except Exception as e:
self.fermenter = FermenterAPI(cbpi) self.notify("Actor Error", "Failed to setup actor %s. Please check the configuraiton" % value.name,
self.core = CoreAPI(cbpi) type="danger", timeout=None)
self.app.logger.error("Initializing of Actor %s failed" % id)
def init(self):
self.core.init() def switch_actor_on(self, id, power=None):
self.step.init() actor = self.cache.get("actors").get(id)
self.actor.init()
self.sensor.init() if actor.state == 1:
# self.kettle.init() return
# self.fermenter.init()
actor.instance.on(power=power)
actor.state = 1
class ActorCore(object): if power is not None:
key = "actor_types"
actor.power = power
def __init__(self, cbpi): self.emit("SWITCH_ACTOR", actor)
self.cbpi = cbpi
self.cbpi.cache["actors"] = {} def actor_power(self, id, power=100):
self.cbpi.cache[self.key] = {} actor = self.cache.get("actors").get(id)
actor.instance.set_power(power=power)
def init(self): actor.power = power
for key, value in self.cbpi.cache["actors"].iteritems(): self.emit("SWITCH_ACTOR", actor)
self.init_one(key)
def switch_actor_off(self, id):
def init_one(self, id): actor = self.cache.get("actors").get(id)
try:
if actor.state == 0:
actor = self.cbpi.cache["actors"][id] return
clazz = self.cbpi.cache[self.key].get(actor.type)["class"] actor.instance.off()
cfg = actor.config.copy() actor.state = 0
cfg.update(dict(cbpi=self.cbpi, id=id)) self.emit("SWITCH_ACTOR", actor)
self.cbpi.cache["actors"][id].instance = clazz(**cfg)
actor.state = 0 class SensorAPI(object):
actor.power = 100
self.cbpi.emit("INIT_ACTOR", id=id) def init_sensors(self):
except Exception as e: '''
print e Initialize all sensors
self.cbpi._app.logger.error(e) :return:
'''
def stop_one(self, id):
self.cbpi.cache["actors"][id]["instance"].stop() self.app.logger.info("Init Sensors")
self.cbpi.emit("STOP_ACTOR", id=id)
t = self.cache.get("sensor_types")
def on(self, id, power=100): for key, value in t.iteritems():
try: value.get("class").init_global()
actor = self.cbpi.cache["actors"].get(int(id))
actor.instance.on() for key in self.cache.get("sensors"):
actor.state = 1 self.init_sensor(key)
actor.power = power
self.cbpi.ws_emit("SWITCH_ACTOR", actor) def stop_sensor(self, id):
self.cbpi.emit("SWITCH_ACTOR_ON", id=id, power=power)
return True try:
except Exception as e: self.cache.get("sensors").get(id).instance.stop()
print e except Exception as e:
return False
self.app.logger.info("Stop Sensor Error")
def off(self, id): pass
try:
actor = self.cbpi.cache["actors"].get(int(id))
actor.instance.off() def init_sensor(self, id):
actor.state = 0 '''
self.cbpi.ws_emit("SWITCH_ACTOR", actor) initialize sensor by id
self.cbpi.emit("SWITCH_ACTOR_OFF", id=id) :param id:
return True :return:
except Exception as e: '''
print e
return False def start_active_sensor(instance):
'''
def toggle(self, id): start active sensors as background job
if self.cbpi.cache.get("actors").get(id).state == 0: :param instance:
self.on(id) :return:
else: '''
self.off(id) instance.execute()
def power(self, id, power): try:
try: if id in self.cache.get("sensor_instances"):
actor = self.cbpi.cache["actors"].get(int(id)) self.cache.get("sensor_instances").get(id).stop()
actor.instance.power(power) value = self.cache.get("sensors").get(id)
actor.power = power
self.cbpi.ws_emit("SWITCH_ACTOR", actor) cfg = value.config.copy()
self.cbpi.emit("SWITCH_ACTOR_POWER_CHANGE", id=id, power=power) cfg.update(dict(api=self, id=id, name=value.name))
return True clazz = self.cache.get("sensor_types").get(value.type).get("class")
except Exception as e: value.instance = clazz(**cfg)
print e value.instance.init()
return False if isinstance(value.instance, SensorPassive):
# Passive Sensors
def action(self, id, method): value.mode = "P"
self.cbpi.cache.get("actors").get(id).instance.__getattribute__(method)() else:
# Active Sensors
value.mode = "A"
def toggle_timeout(self, id, seconds): t = self.socketio.start_background_task(target=start_active_sensor, instance=value.instance)
def toggle( id, seconds): except Exception as e:
self.cbpi.cache.get("actors").get(int(id)).timer = int(time.time()) + int(seconds)
self.toggle(int(id)) self.notify("Sensor Error", "Failed to setup Sensor %s. Please check the configuraiton" % value.name, type="danger", timeout=None)
self.cbpi.sleep(seconds) self.app.logger.error("Initializing of Sensor %s failed" % id)
self.cbpi.cache.get("actors").get(int(id)).timer = None
self.toggle(int(id)) def receive_sensor_value(self, id, value):
job = self.cbpi._socketio.start_background_task(target=toggle, id=id, seconds=seconds) self.emit("SENSOR_UPDATE", self.cache.get("sensors")[id])
self.save_to_file(id, value)
def get_state(self, actor_id):
pass def save_to_file(self, id, value, prefix="sensor"):
filename = "./logs/%s_%s.log" % (prefix, str(id))
formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime())
class SensorCore(object): msg = str(formatted_time) + "," +str(value) + "\n"
key = "sensor_types"
with open(filename, "a") as file:
def __init__(self, cbpi): file.write(msg)
self.cbpi = cbpi
self.cbpi.cache["sensors"] = {} def log_action(self, text):
self.cbpi.cache["sensor_instances"] = {} filename = "./logs/action.log"
self.cbpi.cache["sensor_types"] = {} formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime())
with open(filename, "a") as file:
def init(self): text = text.encode("utf-8")
for key, value in self.cbpi.cache["sensors"].iteritems(): file.write("%s,%s\n" % (formatted_time, text))
self.init_one(key)
def shutdown_sensor(self, id):
def init_one(self, id): self.cache.get("sensors")[id].stop()
try:
sensor = self.cbpi.cache["sensors"][id]
clazz = self.cbpi.cache[self.key].get(sensor.type)["class"] def get_sensor_value(self, id):
cfg = sensor.config.copy() try:
cfg.update(dict(cbpi=self.cbpi, id=id)) id = int(id)
self.cbpi.cache["sensors"][id].instance = clazz(**cfg) return float(self.cache.get("sensors")[id].instance.last_value)
self.cbpi.cache["sensors"][id].instance.init() except Exception as e:
self.cbpi.emit("INIT_SENSOR", id=id) return None
def job(obj): class CacheAPI(object):
obj.execute()
def get_sensor(self, id):
t = self.cbpi._socketio.start_background_task(target=job, obj=self.cbpi.cache["sensors"][id].instance) try:
self.cbpi.emit("INIT_SENSOR", id=id) return self.cache["sensors"][id]
except:
except Exception as e: return None
print "ERROR"
self.cbpi._app.logger.error(e) def get_actor(self, id):
try:
def stop_one(self, id): return self.cache["actors"][id]
except:
self.cbpi.cache["sensors"][id].instance.stop() return None
self.cbpi.emit("STOP_SENSOR", id=id)
class CraftBeerPi(ActorAPI, SensorAPI):
def get_value(self, sensorid):
try: cache = {
return self.cbpi.cache["sensors"][sensorid].instance.value "init": {},
except: "config": {},
return None "actor_types": {},
"sensor_types": {},
def get_state(self, actor_id): "sensors": {},
pass "sensor_instances": {},
"init": [],
def write_log(self, id, value, prefix="sensor"): "background":[],
filename = "./logs/%s_%s.log" % (prefix, str(id)) "step_types": {},
formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime()) "controller_types": {},
msg = str(formatted_time) + "," + str(value) + "\n" "messages": [],
"plugins": {},
with open(filename, "a") as file: "fermentation_controller_types": {},
file.write(msg) "fermenter_task": {}
}
def action(self, id, method): buzzer = None
self.cbpi.cache.get("sensors").get(id).instance.__getattribute__(method)() eventbus = {}
# constructor
class BrewingCore(object): def __init__(self, app, socketio):
def __init__(self, cbpi): self.app = app
self.cbpi = cbpi self.socketio = socketio
self.cbpi.cache["step_types"] = {}
self.cbpi.cache["controller_types"] = {}
def emit(self, key, data):
def log_action(self, text): self.socketio.emit(key, data, namespace='/brew')
filename = "./logs/action.log"
formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime()) def notify(self, headline, message, type="success", timeout=5000):
with open(filename, "a") as file: self.beep()
text = text.encode("utf-8") msg = {"id": str(uuid.uuid1()), "type": type, "headline": headline, "message": message, "timeout": timeout}
file.write("%s,%s\n" % (formatted_time, text)) self.emit_message(msg)
def get_controller(self, name): def beep(self):
return self.cbpi.cache["controller_types"].get(name) if self.buzzer is not None:
self.buzzer.beep()
def set_target_temp(self, id, temp):
self.cbpi.cache.get("kettle")[id].target_temp = float(temp)
Kettle.update(**self.cbpi.cache.get("kettle")[id].__dict__) def add_cache_callback(self, key, method):
self.cbpi.ws_emit("UPDATE_KETTLE_TARGET_TEMP", {"id": id, "target_temp": temp}) method.callback = True
self.cbpi.emit("SET_KETTLE_TARGET_TEMP", id=id, temp=temp) self.cache[key] = method
def get_config_parameter(self, key, default):
def toggle_automatic(self, id): cfg = self.cache.get("config").get(key)
kettle = self.cbpi.cache.get("kettle")[id]
if kettle.state is False: if cfg is None:
# Start controller return default
if kettle.logic is not None: else:
cfg = kettle.config.copy() return cfg.value
cfg.update(dict(api=cbpi, kettle_id=kettle.id, heater=kettle.heater, sensor=kettle.sensor))
instance = self.get_controller(kettle.logic).get("class")(**cfg) def set_config_parameter(self, name, value):
instance.init() from modules.config import Config
kettle.instance = instance with self.app.app_context():
update_data = {"name": name, "value": value}
def run(instance): self.cache.get("config")[name].__dict__.update(**update_data)
instance.run() c = Config.update(**update_data)
self.emit("UPDATE_CONFIG", c)
t = self.cbpi._socketio.start_background_task(target=run, instance=instance)
kettle.state = not kettle.state
self.cbpi.ws_emit("UPDATE_KETTLE", cbpi.cache.get("kettle").get(id)) def add_config_parameter(self, name, value, type, description, options=None):
self.cbpi.emit("KETTLE_CONTROLLER_STARTED", id=id) from modules.config import Config
else: with self.app.app_context():
# Stop controller c = Config.insert(**{"name":name, "value": value, "type": type, "description": description, "options": options})
kettle.instance.stop() if self.cache.get("config") is not None:
kettle.state = not kettle.state self.cache.get("config")[c.name] = c
self.cbpi.ws_emit("UPDATE_KETTLE", cbpi.cache.get("kettle").get(id))
self.cbpi.emit("KETTLE_CONTROLLER_STOPPED", id=id) def clear_cache(self, key, is_array=False):
if is_array:
self.cache[key] = []
class FermentationCore(object): else:
def __init__(self, cbpi): self.cache[key] = {}
self.cbpi = cbpi
self.cbpi.cache["fermenter"] = {} # helper method for parsing props
self.cbpi.cache["fermentation_controller_types"] = {} def __parseProps(self, key, cls):
name = cls.__name__
def get_controller(self, name): self.cache[key][name] = {"name": name, "class": cls, "properties": [], "actions": []}
return self.cbpi.cache["fermentation_controller_types"].get(name) tmpObj = cls()
members = [attr for attr in dir(tmpObj) if not callable(getattr(tmpObj, attr)) and not attr.startswith("__")]
for m in members:
class CraftBeerPI(object): if isinstance(tmpObj.__getattribute__(m), Property.Number):
cache = {} t = tmpObj.__getattribute__(m)
eventbus = {} self.cache[key][name]["properties"].append(
{"name": m, "label": t.label, "type": "number", "configurable": t.configurable, "description": t.description, "default_value": t.default_value})
def __init__(self): elif isinstance(tmpObj.__getattribute__(m), Property.Text):
FORMAT = '%(asctime)-15s - %(levelname)s - %(message)s' t = tmpObj.__getattribute__(m)
logging.basicConfig(filename='./logs/app.log', level=logging.INFO, format=FORMAT) self.cache[key][name]["properties"].append(
self.cache["messages"] = [] {"name": m, "label": t.label, "type": "text", "configurable": t.configurable, "default_value": t.default_value, "description": t.description})
self.cache["version"] = "3.1" elif isinstance(tmpObj.__getattribute__(m), Property.Select):
self.modules = {} t = tmpObj.__getattribute__(m)
self.cache[key][name]["properties"].append(
self.addon = Addon(self) {"name": m, "label": t.label, "type": "select", "configurable": True, "options": t.options, "description": t.description})
self.actor = ActorCore(self) elif isinstance(tmpObj.__getattribute__(m), Property.Actor):
self.sensor = SensorCore(self) t = tmpObj.__getattribute__(m)
self.brewing = BrewingCore(self) self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "actor", "configurable": t.configurable, "description": t.description})
self.fermentation = FermentationCore(self) elif isinstance(tmpObj.__getattribute__(m), Property.Sensor):
self._app = Flask(__name__) t = tmpObj.__getattribute__(m)
self._app.secret_key = 'Cr4ftB33rP1' self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "sensor", "configurable": t.configurable, "description": t.description})
self._app.json_encoder = ComplexEncoder elif isinstance(tmpObj.__getattribute__(m), Property.Kettle):
self._socketio = SocketIO(self._app, json=json, logging=False) t = tmpObj.__getattribute__(m)
self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "kettle", "configurable": t.configurable, "description": t.description})
@self._app.route('/')
def index(): for name, method in cls.__dict__.iteritems():
return redirect('ui') if hasattr(method, "action"):
label = method.__getattribute__("label")
def run(self): self.cache[key][cls.__name__]["actions"].append({"method": name, "label": label})
self.__init_db()
self.loadPlugins()
self.addon.init() return cls
self.sensor.init()
self.actor.init()
self.beep() def actor(self, cls):
try: return self.__parseProps("actor_types", cls)
port = int(cbpi.get_config_parameter('port', '5000'))
except ValueError:
port = 5000
print port def actor2(self, description="", power=True, **options):
self._socketio.run(self._app, host='0.0.0.0', port=port)
def decorator(f):
def beep(self): print f()
self.buzzer.beep() print f
print options
def sleep(self, seconds): print description
self._socketio.sleep(seconds) return f
return decorator
def notify(self, headline, message, type="success", timeout=5000):
msg = {"id": str(uuid.uuid1()), "type": type, "headline": headline, "message": message, "timeout": timeout} def sensor(self, cls):
self.ws_emit("NOTIFY", msg) return self.__parseProps("sensor_types", cls)
def ws_emit(self, key, data): def controller(self, cls):
self._socketio.emit(key, data, namespace='/brew') return self.__parseProps("controller_types", cls)
def __init_db(self, ): def fermentation_controller(self, cls):
return self.__parseProps("fermentation_controller_types", cls)
with self._app.app_context():
db = self.get_db() def get_controller(self, name):
try: return self.cache["controller_types"].get(name)
with self._app.open_resource('../../config/schema.sql', mode='r') as f:
db.cursor().executescript(f.read()) def get_fermentation_controller(self, name):
db.commit() return self.cache["fermentation_controller_types"].get(name)
except Exception as e:
pass # Step action
def action(self,label):
def nocache(self, view): def real_decorator(func):
@wraps(view) func.action = True
def no_cache(*args, **kwargs): func.label = label
response = make_response(view(*args, **kwargs)) return func
response.headers['Last-Modified'] = datetime.now() return real_decorator
response.headers[
'Cache-Control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0' # step decorator
response.headers['Pragma'] = 'no-cache' def step(self, cls):
response.headers['Expires'] = '-1'
return response key = "step_types"
name = cls.__name__
return update_wrapper(no_cache, view) self.cache[key][name] = {"name": name, "class": cls, "properties": [], "actions": []}
def get_db(self): tmpObj = cls()
db = getattr(g, '_database', None) members = [attr for attr in dir(tmpObj) if not callable(getattr(tmpObj, attr)) and not attr.startswith("__")]
if db is None: for m in members:
def dict_factory(cursor, row): if isinstance(tmpObj.__getattribute__(m), StepProperty.Number):
d = {} t = tmpObj.__getattribute__(m)
for idx, col in enumerate(cursor.description): self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "number", "configurable": t.configurable, "default_value": t.default_value, "description": t.description})
d[col[0]] = row[idx] elif isinstance(tmpObj.__getattribute__(m), StepProperty.Text):
return d t = tmpObj.__getattribute__(m)
self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "text", "configurable": t.configurable, "default_value": t.default_value, "description": t.description})
db = g._database = sqlite3.connect('craftbeerpi.db') elif isinstance(tmpObj.__getattribute__(m), StepProperty.Select):
db.row_factory = dict_factory t = tmpObj.__getattribute__(m)
return db self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "select", "configurable": True, "options": t.options, "description": t.description})
elif isinstance(tmpObj.__getattribute__(m), StepProperty.Actor):
def add_cache_callback(self, key, method): t = tmpObj.__getattribute__(m)
method.callback = True self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "actor", "configurable": t.configurable, "description": t.description})
self.cache[key] = method elif isinstance(tmpObj.__getattribute__(m), StepProperty.Sensor):
t = tmpObj.__getattribute__(m)
def get_config_parameter(self, key, default): self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "sensor", "configurable": t.configurable, "description": t.description})
cfg = self.cache["config"].get(key) elif isinstance(tmpObj.__getattribute__(m), StepProperty.Kettle):
if cfg is None: t = tmpObj.__getattribute__(m)
return default self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "kettle", "configurable": t.configurable, "description": t.description})
else:
return cfg.value for name, method in cls.__dict__.iteritems():
if hasattr(method, "action"):
def emit(self, key, **kwargs): label = method.__getattribute__("label")
self.cache[key][cls.__name__]["actions"].append({"method": name, "label": label})
if self.eventbus.get(key) is not None:
for value in self.eventbus[key]: return cls
if value["async"] is False:
value["function"](**kwargs)
else: # Event Bus
t = self.cbpi._socketio.start_background_task(target=value["function"], **kwargs) def event(self, name, async=False):
def loadPlugins(self): def real_decorator(function):
for filename in os.listdir("./modules/plugins"): if self.eventbus.get(name) is None:
self.eventbus[name] = []
if os.path.isdir("./modules/plugins/" + filename) is False: self.eventbus[name].append({"function": function, "async": async})
continue def wrapper(*args, **kwargs):
try: return function(*args, **kwargs)
self.modules[filename] = import_module("modules.plugins.%s" % (filename)) return wrapper
except Exception as e: return real_decorator
self.notify("Failed to load plugin %s " % filename, str(e), type="danger", timeout=None) def emit_message(self, message):
self.emit_event(name="MESSAGE", message=message)
cbpi = CraftBeerPI() def emit_event(self, name, **kwargs):
addon = cbpi.addon for i in self.eventbus.get(name, []):
if i["async"] is False:
i["function"](**kwargs)
else:
t = self.socketio.start_background_task(target=i["function"], **kwargs)
# initializer decorator
def initalizer(self, order=0):
def real_decorator(function):
self.cache["init"].append({"function": function, "order": order})
def wrapper(*args, **kwargs):
return function(*args, **kwargs)
return wrapper
return real_decorator
def try_catch(self, errorResult="ERROR"):
def real_decorator(function):
def wrapper(*args, **kwargs):
try:
return function(*args, **kwargs)
except:
self.app.logger.error("Exception in function %s. Return default %s" % (function.__name__, errorResult))
return errorResult
return wrapper
return real_decorator
def nocache(self, view):
@wraps(view)
def no_cache(*args, **kwargs):
response = make_response(view(*args, **kwargs))
response.headers['Last-Modified'] = datetime.now()
response.headers[
'Cache-Control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0'
response.headers['Pragma'] = 'no-cache'
response.headers['Expires'] = '-1'
return response
return update_wrapper(no_cache, view)
def init_kettle(self, id):
try:
value = self.cache.get("kettle").get(id)
value["state"] = False
except:
self.notify("Kettle Setup Faild", "Please check %s configuration" % value.name, type="danger", timeout=None)
self.app.logger.error("Initializing of Kettle %s failed" % id)
def run_init(self):
'''
call all initialziers after startup
:return:
'''
self.app.logger.info("Invoke Init")
self.cache["init"] = sorted(self.cache["init"], key=lambda k: k['order'])
for i in self.cache.get("init"):
self.app.logger.info("INITIALIZER - METHOD %s PAHT %s: " % (i.get("function").__name__, str(inspect.getmodule(i.get("function")).__file__) ))
i.get("function")(self)
def backgroundtask(self, key, interval, config_parameter=None):
'''
Background Task Decorator
:param key:
:param interval:
:param config_parameter:
:return:
'''
def real_decorator(function):
self.cache["background"].append({"function": function, "key": key, "interval": interval, "config_parameter": config_parameter})
def wrapper(*args, **kwargs):
return function(*args, **kwargs)
return wrapper
return real_decorator
def run_background_processes(self):
'''
call all background task after startup
:return:
'''
self.app.logger.info("Start Background")
def job(interval, method):
while True:
try:
method(self)
except Exception as e:
self.app.logger.error("Exception" + method.__name__ + ": " + str(e))
self.socketio.sleep(interval)
for value in self.cache.get("background"):
t = self.socketio.start_background_task(target=job, interval=value.get("interval"), method=value.get("function"))
Executable → Regular
+134 -133
View File
@@ -1,133 +1,134 @@
import sqlite3 import sqlite3
from flask import json, g
from flask import json, g
def get_db():
db = getattr(g, '_database', None) def get_db():
if db is None: db = getattr(g, '_database', None)
def dict_factory(cursor, row): if db is None:
d = {} def dict_factory(cursor, row):
for idx, col in enumerate(cursor.description): d = {}
d[col[0]] = row[idx] for idx, col in enumerate(cursor.description):
return d d[col[0]] = row[idx]
db = g._database = sqlite3.connect('craftbeerpi.db') return d
db.row_factory = dict_factory db = g._database = sqlite3.connect('craftbeerpi.db')
return db db.row_factory = dict_factory
return db
class DBModel(object):
class DBModel(object):
__priamry_key__ = "id"
__as_array__ = False __priamry_key__ = "id"
__order_by__ = None __as_array__ = False
__json_fields__ = [] __order_by__ = None
__json_fields__ = []
def __init__(self, args):
def __init__(self, args):
self.__setattr__(self.__priamry_key__, args.get(self.__priamry_key__))
for f in self.__fields__: self.__setattr__(self.__priamry_key__, args.get(self.__priamry_key__))
if f in self.__json_fields__: for f in self.__fields__:
if args.get(f) is not None: if f in self.__json_fields__:
if args.get(f) is not None:
if isinstance(args.get(f) , dict) or isinstance(args.get(f) , list) :
self.__setattr__(f, args.get(f)) if isinstance(args.get(f) , dict) or isinstance(args.get(f) , list) :
else: self.__setattr__(f, args.get(f))
self.__setattr__(f, json.loads(args.get(f))) else:
else: self.__setattr__(f, json.loads(args.get(f)))
self.__setattr__(f, None) else:
else: self.__setattr__(f, None)
self.__setattr__(f, args.get(f)) else:
self.__setattr__(f, args.get(f))
@classmethod
def get_all(cls): @classmethod
cur = get_db().cursor() def get_all(cls):
if cls.__order_by__ is not None: cur = get_db().cursor()
if cls.__order_by__ is not None:
cur.execute("SELECT * FROM %s ORDER BY %s.'%s'" % (cls.__table_name__,cls.__table_name__,cls.__order_by__))
else: cur.execute("SELECT * FROM %s ORDER BY %s.'%s'" % (cls.__table_name__,cls.__table_name__,cls.__order_by__))
cur.execute("SELECT * FROM %s" % cls.__table_name__) else:
cur.execute("SELECT * FROM %s" % cls.__table_name__)
if cls.__as_array__ is True:
result = [] if cls.__as_array__ is True:
for r in cur.fetchall(): result = []
for r in cur.fetchall():
result.append( cls(r))
else: result.append( cls(r))
result = {} else:
for r in cur.fetchall(): result = {}
result[r.get(cls.__priamry_key__)] = cls(r) for r in cur.fetchall():
return result result[r.get(cls.__priamry_key__)] = cls(r)
return result
@classmethod
def get_one(cls, id): @classmethod
cur = get_db().cursor() def get_one(cls, id):
cur.execute("SELECT * FROM %s WHERE %s = ?" % (cls.__table_name__, cls.__priamry_key__), (id,)) cur = get_db().cursor()
r = cur.fetchone() cur.execute("SELECT * FROM %s WHERE %s = ?" % (cls.__table_name__, cls.__priamry_key__), (id,))
if r is not None: r = cur.fetchone()
return cls(r) if r is not None:
else: return cls(r)
return None else:
return None
@classmethod
def delete(cls, id): @classmethod
cur = get_db().cursor() def delete(cls, id):
cur.execute("DELETE FROM %s WHERE %s = ? " % (cls.__table_name__, cls.__priamry_key__), (id,)) cur = get_db().cursor()
get_db().commit() cur.execute("DELETE FROM %s WHERE %s = ? " % (cls.__table_name__, cls.__priamry_key__), (id,))
get_db().commit()
@classmethod
def insert(cls, **kwargs): @classmethod
cur = get_db().cursor() def insert(cls, **kwargs):
cur = get_db().cursor()
if cls.__priamry_key__ is not None and kwargs.has_key(cls.__priamry_key__):
query = "INSERT INTO %s (%s, %s) VALUES (?, %s)" % ( if cls.__priamry_key__ is not None and kwargs.has_key(cls.__priamry_key__):
cls.__table_name__, query = "INSERT INTO %s (%s, %s) VALUES (?, %s)" % (
cls.__priamry_key__, cls.__table_name__,
', '.join("'%s'" % str(x) for x in cls.__fields__), cls.__priamry_key__,
', '.join(['?'] * len(cls.__fields__))) ', '.join("'%s'" % str(x) for x in cls.__fields__),
data = () ', '.join(['?'] * len(cls.__fields__)))
data = data + (kwargs.get(cls.__priamry_key__),) data = ()
for f in cls.__fields__: data = data + (kwargs.get(cls.__priamry_key__),)
if f in cls.__json_fields__: for f in cls.__fields__:
data = data + (json.dumps(kwargs.get(f)),) if f in cls.__json_fields__:
else: data = data + (json.dumps(kwargs.get(f)),)
data = data + (kwargs.get(f),) else:
else: data = data + (kwargs.get(f),)
else:
query = 'INSERT INTO %s (%s) VALUES (%s)' % (
cls.__table_name__, query = 'INSERT INTO %s (%s) VALUES (%s)' % (
', '.join("'%s'" % str(x) for x in cls.__fields__), cls.__table_name__,
', '.join(['?'] * len(cls.__fields__))) ', '.join("'%s'" % str(x) for x in cls.__fields__),
', '.join(['?'] * len(cls.__fields__)))
data = ()
for f in cls.__fields__: data = ()
if f in cls.__json_fields__: for f in cls.__fields__:
data = data + (json.dumps(kwargs.get(f)),) if f in cls.__json_fields__:
else: data = data + (json.dumps(kwargs.get(f)),)
data = data + (kwargs.get(f),) else:
data = data + (kwargs.get(f),)
cur.execute(query, data)
get_db().commit() cur.execute(query, data)
i = cur.lastrowid get_db().commit()
kwargs["id"] = i i = cur.lastrowid
kwargs["id"] = i
return cls(kwargs)
return cls(kwargs)
@classmethod
def update(cls, **kwargs): @classmethod
cur = get_db().cursor() def update(cls, **kwargs):
query = 'UPDATE %s SET %s WHERE %s = ?' % ( cur = get_db().cursor()
cls.__table_name__, query = 'UPDATE %s SET %s WHERE %s = ?' % (
', '.join("'%s' = ?" % str(x) for x in cls.__fields__),cls.__priamry_key__) cls.__table_name__,
', '.join("'%s' = ?" % str(x) for x in cls.__fields__),cls.__priamry_key__)
data = ()
for f in cls.__fields__: data = ()
if f in cls.__json_fields__: for f in cls.__fields__:
data = data + (json.dumps(kwargs.get(f)),) if f in cls.__json_fields__:
else: data = data + (json.dumps(kwargs.get(f)),)
data = data + (kwargs.get(f),) else:
data = data + (kwargs.get(f),)
data = data + (kwargs.get(cls.__priamry_key__),)
cur.execute(query, data) data = data + (kwargs.get(cls.__priamry_key__),)
get_db().commit() cur.execute(query, data)
return cls(kwargs) get_db().commit()
return cls(kwargs)
@@ -1,11 +1,11 @@
import sqlite3 import sqlite3
import os import os
from modules.core.core import cbpi from modules import cbpi
from modules.core.db import get_db from db import get_db
def execute_file(curernt_version, data): def execute_file(curernt_version, data):
if curernt_version >= data["version"]: if curernt_version >= data["version"]:
cbpi._app.logger.info("SKIP DB FILE: %s" % data["file"]) cbpi.app.logger.info("SKIP DB FILE: %s" % data["file"])
return return
try: try:
with sqlite3.connect("craftbeerpi.db") as conn: with sqlite3.connect("craftbeerpi.db") as conn:
@@ -19,13 +19,13 @@ def execute_file(curernt_version, data):
conn.commit() conn.commit()
except sqlite3.OperationalError as err: except sqlite3.OperationalError as err:
print "EXCEPT"
print err print err
@cbpi.addon.core.initializer(order=-9999) @cbpi.initalizer(order=-9999)
def init(cbpi): def init(app=None):
with cbpi._app.app_context(): with cbpi.app.app_context():
conn = get_db() conn = get_db()
cur = conn.cursor() cur = conn.cursor()
current_version = None current_version = None
@@ -37,8 +37,11 @@ def init(cbpi):
pass pass
result = [] result = []
for filename in os.listdir("./update"): for filename in os.listdir("./update"):
if filename.endswith(".sql"): if filename.endswith(".sql"):
d = {"version": int(filename[:filename.index('_')]), "file": filename} d = {"version": int(filename[:filename.index('_')]), "file": filename}
result.append(d) result.append(d)
execute_file(current_version, d) execute_file(current_version, d)
+108
View File
@@ -0,0 +1,108 @@
# -*- coding: utf-8 -*-
class Base(object):
__dirty = False
@classmethod
def init_global(cls):
pass
def get_config_parameter(self, key, default_value):
return self.api.get_config_parameter(key, default_value)
def sleep(self, seconds):
self.api.socketio.sleep(seconds)
def init(self):
pass
def stop(self):
pass
def update(self, **kwds):
pass
def __init__(self, *args, **kwds):
for a in kwds:
super(Base, self).__setattr__(a, kwds.get(a))
self.api = kwds.get("api")
self.id = kwds.get("id")
self.value = None
self.__dirty = False
def __setattr__(self, name, value):
if name != "_Base__dirty":
self.__dirty = True
super(Base, self).__setattr__(name, value)
else:
super(Base, self).__setattr__(name, value)
class SensorBase(Base):
last_value = 0
def init(self):
print "INIT Base SENSOR"
def stop(self):
print "STOP SENSOR"
def data_received(self, data):
self.last_value = data
self.api.receive_sensor_value(self.id, data)
def get_unit(self):
if self.get_config_parameter("unit", "C") == "C":
return "°C"
else:
return "°F"
def get_value(self):
return {"value": self.last_value, "unit": self.get_unit()}
class SensorActive(SensorBase):
__running = False
def is_running(self):
return self.__running
def init(self):
self.__running = True
def stop(self):
self.__running = False
def execute(self):
pass
class SensorPassive(SensorBase):
def init(self):
print "INIT PASSIV SENSOR"
pass
def read(self):
return 0
class ActorBase(Base):
def state(self):
return 1
def set_power(self, power):
pass
def on(self, power=0):
pass
def off(self):
pass
+68 -48
View File
@@ -1,48 +1,68 @@
class PropertyType(object): class PropertyType(object):
pass pass
class Property(object): class Property(object):
class Select(PropertyType): class Select(PropertyType):
def __init__(self, label, options, description=""): def __init__(self, label, options, description=""):
PropertyType.__init__(self) PropertyType.__init__(self)
self.label = label self.label = label
self.options = options self.options = options
self.description = description self.description = description
class Number(PropertyType): class Number(PropertyType):
def __init__(self, label, configurable=False, default_value=None, unit="", description=""): def __init__(self, label, configurable=False, default_value=None, unit="", description=""):
PropertyType.__init__(self) PropertyType.__init__(self)
self.label = label self.label = label
self.configurable = configurable self.configurable = configurable
self.default_value = default_value self.default_value = default_value
self.description = description self.description = description
class Text(PropertyType): class Text(PropertyType):
def __init__(self, label, configurable=False, required=False, default_value="", description=""): def __init__(self, label, configurable=False, default_value="", description=""):
PropertyType.__init__(self) PropertyType.__init__(self)
self.label = label self.label = label
self.required = required self.configurable = configurable
self.configurable = configurable self.default_value = default_value
self.default_value = default_value self.description = description
self.description = description
class Actor(PropertyType):
class Actor(PropertyType): def __init__(self, label, description=""):
def __init__(self, label, description=""): PropertyType.__init__(self)
PropertyType.__init__(self) self.label = label
self.label = label self.configurable = True
self.configurable = True self.description = description
self.description = description
class Sensor(PropertyType):
class Sensor(PropertyType): def __init__(self, label, description=""):
def __init__(self, label, description=""): PropertyType.__init__(self)
PropertyType.__init__(self) self.label = label
self.label = label self.configurable = True
self.configurable = True self.description = description
self.description = description
class Kettle(PropertyType):
class Kettle(PropertyType): def __init__(self, label, description=""):
def __init__(self, label, description=""): PropertyType.__init__(self)
PropertyType.__init__(self) self.label = label
self.label = label self.configurable = True
self.configurable = True self.description = description
self.description = description
class StepProperty(Property):
class Actor(PropertyType):
def __init__(self, label, description=""):
PropertyType.__init__(self)
self.label = label
self.configurable = True
self.description = description
class Sensor(PropertyType):
def __init__(self, label, description=""):
PropertyType.__init__(self)
self.label = label
self.configurable = True
self.description = description
class Kettle(PropertyType):
def __init__(self, label, description=""):
PropertyType.__init__(self)
self.label = label
self.configurable = True
self.description = description
Executable → Regular
+146
View File
@@ -0,0 +1,146 @@
from modules import cbpi
from modules.core.props import StepProperty, Property
import time
class NotificationAPI(object):
def notify(self, headline, message, type="success", timeout=5000):
self.api.notify(headline, message, type, timeout)
class ActorAPI(NotificationAPI):
@cbpi.try_catch(None)
def actor_on(self, id, power=100):
self.api.switch_actor_on(int(id), power=power)
@cbpi.try_catch(None)
def actor_off(self, id):
self.api.switch_actor_off(int(id))
@cbpi.try_catch(None)
def actor_power(self, id, power):
self.api.actor_power(int(id), power)
class SensorAPI(NotificationAPI):
@cbpi.try_catch(None)
def get_sensor_value(self, id):
return cbpi.get_sensor_value(id)
class KettleAPI(NotificationAPI):
@cbpi.try_catch(None)
def get_kettle_temp(self, id=None):
id = int(id)
if id is None:
id = self.kettle_id
return cbpi.get_sensor_value(int(self.api.cache.get("kettle").get(id).sensor))
@cbpi.try_catch(None)
def get_target_temp(self, id=None):
id = int(id)
if id is None:
id = self.kettle_id
return self.api.cache.get("kettle").get(id).target_temp
def set_target_temp(self, temp, id=None):
temp = float(temp)
try:
if id is None:
self.api.emit_event("SET_TARGET_TEMP", id=self.kettle_id, temp=temp)
else:
self.api.emit_event("SET_TARGET_TEMP", id=id, temp=temp)
except Exception as e:
self.notify("Faild to set Target Temp", "", type="warning")
class Timer(object):
timer_end = Property.Number("TIMER_END", configurable=False)
def start_timer(self, timer):
if self.timer_end is not None:
return
self.timer_end = int(time.time()) + timer
def stop_timer(self):
if self.timer_end is not None:
self.timer_end = None
def is_timer_running(self):
if self.timer_end is not None:
return True
else:
return False
def timer_remaining(self):
if self.timer_end is not None:
return self.timer_end - int(time.time())
else:
return None
def is_timer_finished(self):
if self.timer_end is None:
return None
if self.timer_end <= int(time.time()):
return True
else:
return False
class StepBase(Timer, ActorAPI, SensorAPI, KettleAPI):
__dirty = False
managed_fields = []
n = False
def next(self):
self.n = True
def init(self):
pass
def finish(self):
pass
def reset(self):
pass
def execute(self):
print "-------------"
print "Step Info"
print "Kettle ID: %s" % self.kettle_id
print "ID: %s" % self.id
def __init__(self, *args, **kwds):
for a in kwds:
super(StepBase, self).__setattr__(a, kwds.get(a))
self.api = kwds.get("api")
self.id = kwds.get("id")
self.name = kwds.get("name")
self.kettle_id = kwds.get("kettleid")
self.value = None
self.__dirty = False
def is_dirty(self):
return self.__dirty
def reset_dirty(self):
self.__dirty = False
def __setattr__(self, name, value):
if name != "_StepBase__dirty" and name in self.managed_fields:
self.__dirty = True
super(StepBase, self).__setattr__(name, value)
else:
super(StepBase, self).__setattr__(name, value)
View File
-134
View File
@@ -1,134 +0,0 @@
from modules.core.db import DBModel, get_db
from flask import json
class Kettle(DBModel):
__fields__ = ["name","sensor", "heater", "automatic", "logic", "config", "agitator", "target_temp"]
__table_name__ = "kettle"
__json_fields__ = ["config"]
class Sensor(DBModel):
__fields__ = ["name","type", "config", "hide"]
__table_name__ = "sensor"
__json_fields__ = ["config"]
class Config(DBModel):
__fields__ = ["type", "value", "description", "options"]
__table_name__ = "config"
__json_fields__ = ["options"]
__priamry_key__ = "name"
class Actor(DBModel):
__fields__ = ["name","type", "config", "hide"]
__table_name__ = "actor"
__json_fields__ = ["config"]
class Step(DBModel):
__fields__ = ["name","type", "stepstate", "state", "start", "end", "order", "config"]
__table_name__ = "step"
__json_fields__ = ["config", "stepstate"]
__order_by__ = "order"
__as_array__ = True
@classmethod
def get_max_order(cls):
cur = get_db().cursor()
cur.execute("SELECT max(step.'order') as 'order' FROM %s" % cls.__table_name__)
r = cur.fetchone()
return r.get("order")
@classmethod
def get_by_state(cls, state, order=True):
cur = get_db().cursor()
cur.execute("SELECT * FROM %s WHERE state = ? ORDER BY %s.'order'" % (cls.__table_name__,cls.__table_name__,), state)
r = cur.fetchone()
if r is not None:
return cls(r)
else:
return None
@classmethod
def delete_all(cls):
cur = get_db().cursor()
cur.execute("DELETE FROM %s" % cls.__table_name__)
get_db().commit()
@classmethod
def reset_all_steps(cls):
cur = get_db().cursor()
cur.execute("UPDATE %s SET state = 'I', stepstate = NULL , start = NULL, end = NULL " % cls.__table_name__)
get_db().commit()
@classmethod
def update_state(cls, id, state):
cur = get_db().cursor()
cur.execute("UPDATE %s SET state = ? WHERE id =?" % cls.__table_name__, (state, id))
get_db().commit()
@classmethod
def update_step_state(cls, id, state):
cur = get_db().cursor()
cur.execute("UPDATE %s SET stepstate = ? WHERE id =?" % cls.__table_name__, (json.dumps(state),id))
get_db().commit()
@classmethod
def sort(cls, new_order):
cur = get_db().cursor()
for e in new_order:
cur.execute("UPDATE %s SET '%s' = ? WHERE id = ?" % (cls.__table_name__, "order"), (e[1], e[0]))
get_db().commit()
class Fermenter(DBModel):
__fields__ = ["name", "brewname", "sensor", "sensor2", "sensor3", "heater", "cooler", "logic", "config", "target_temp"]
__table_name__ = "fermenter"
__json_fields__ = ["config"]
class FermenterStep(DBModel):
__fields__ = ["name", "days", "hours", "minutes", "temp", "direction", "order", "state", "start", "end", "timer_start", "fermenter_id"]
__table_name__ = "fermenter_step"
@classmethod
def get_by_fermenter_id(cls, id):
cur = get_db().cursor()
cur.execute("SELECT * FROM %s WHERE fermenter_id = ?" % cls.__table_name__,(id,))
result = []
for r in cur.fetchall():
result.append(cls(r))
return result
@classmethod
def get_max_order(cls,id):
cur = get_db().cursor()
cur.execute("SELECT max(fermenter_step.'order') as 'order' FROM %s WHERE fermenter_id = ?" % cls.__table_name__, (id,))
r = cur.fetchone()
return r.get("order")
@classmethod
def update_state(cls, id, state):
cur = get_db().cursor()
cur.execute("UPDATE %s SET state = ? WHERE id =?" % cls.__table_name__, (state, id))
get_db().commit()
@classmethod
def update_timer(cls, id, timer):
cur = get_db().cursor()
cur.execute("UPDATE %s SET timer_start = ? WHERE id =?" % cls.__table_name__, (timer, id))
get_db().commit()
@classmethod
def get_by_state(cls, state):
cur = get_db().cursor()
cur.execute("SELECT * FROM %s WHERE state = ?" % cls.__table_name__, state)
r = cur.fetchone()
if r is not None:
return cls(r)
else:
return None
@classmethod
def reset_all_steps(cls,id):
cur = get_db().cursor()
cur.execute("UPDATE %s SET state = 'I', start = NULL, end = NULL, timer_start = NULL WHERE fermenter_id = ?" % cls.__table_name__, (id,))
get_db().commit()
@@ -1,19 +0,0 @@
from flask import Blueprint
from modules.core.core import cbpi, addon
from flask_swagger import swagger
from flask import json
from flask import Blueprint
@addon.core.initializer(order=22)
def web(cbpi):
s = Blueprint('web_view', __name__, template_folder='templates', static_folder='static')
@s.route('/', methods=["GET"])
def index():
return s.send_static_file("index.html")
cbpi.addon.core.add_menu_link("JQuery View", "/web_view")
cbpi._app.register_blueprint(s, url_prefix='/web_view')
@@ -1,32 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>CraftBeerPi WebView</title>
</head>
<body>
<div id="root" >
<div id="kettle">
</div>
</div>
<!--script src="static/bundle.js" type="text/javascript"></script-->
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script>
$(document).ready(function() {
$.ajax({
url: "/api/system/dump",
dataType: "json"
}).then(function(data) {
$.each(data.actors, function (i, val) {
$( "#kettle" ).append("<div>"+ val.name +"</div>")
});
});
});
</script>
</body>
</html>
@@ -1,3 +0,0 @@
{
"presets" : ["es2015", "stage-0", "react"]
}
@@ -1,19 +0,0 @@
from flask import Blueprint
from modules.core.core import cbpi, addon
from flask_swagger import swagger
from flask import json
from flask import Blueprint
@addon.core.initializer(order=22)
def web(cbpi):
s = Blueprint('webviewreact', __name__, template_folder='templates', static_folder='static')
@s.route('/', methods=["GET"])
def index():
return s.send_static_file("index.html")
cbpi.addon.core.add_menu_link("ReactJS View", "/webviewreact")
cbpi._app.register_blueprint(s, url_prefix='/webviewreact')
@@ -1,44 +0,0 @@
{
"name": "react-app2",
"version": "0.1.0",
"private": true,
"devDependencies": {
"react-scripts": "0.9.5"
},
"dependencies": {
"axios": "^0.16.1",
"babel-core": "^6.18.2",
"babel-loader": "^6.2.8",
"babel-preset-es2015": "^6.18.0",
"babel-preset-react": "^6.16.0",
"babel-preset-stage-0": "^6.22.0",
"classnames": "^2.2.5",
"highcharts-boost": "^0.1.2",
"highcharts-exporting": "^0.1.2",
"highcharts-more": "^0.1.2",
"immutability-helper": "^2.1.2",
"rc-slider": "^7.0.8",
"react": "^15.5.4",
"react-bootstrap": "^0.30.10",
"react-bs-notifier": "^4.3.2",
"react-dom": "^15.5.4",
"react-fileupload-progress": "^0.4.0",
"react-highcharts": "^12.0.0",
"react-js-diagrams": "^3.1.2",
"react-jsonschema-form": "^0.50.1",
"react-redux": "^5.0.4",
"react-router-dom": "^4.1.1",
"react-sortable-hoc": "^0.6.3",
"redux": "^3.6.0",
"redux-logger": "^3.0.1",
"redux-thunk": "^2.2.0",
"socket.io-client": "^1.7.3",
"webpack": "^1.13.3"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}
@@ -1,15 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>CraftBeerPi WebView</title>
</head>
<body>
<div id="root" >
</div>
<script src="static/bundle.js" type="text/javascript"></script>
</body>
</html>
View File
@@ -1,23 +0,0 @@
from modules.core.core import cbpi, addon
from flask_swagger import swagger
from flask import json
from flask import Blueprint
@addon.core.initializer(order=22)
def hello(cbpi):
s = Blueprint('react', __name__, template_folder='templates', static_folder='static')
@s.route('/', methods=["GET"])
def index():
return s.send_static_file("index.html")
@s.route('/swagger.json', methods=["GET"])
def spec():
swag = swagger(cbpi._app)
swag['info']['version'] = "3.0"
swag['info']['title'] = "CraftBeerPi"
return json.dumps(swag)
cbpi.addon.core.add_menu_link("Swagger API", "/swagger")
cbpi._app.register_blueprint(s, url_prefix='/swagger')
Binary file not shown.

Before

Width:  |  Height:  |  Size: 445 B

@@ -1,95 +0,0 @@
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="./static/swagger-ui.css" >
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
<style>
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}
body {
margin:0;
background: #fafafa;
}
</style>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position:absolute;width:0;height:0">
<defs>
<symbol viewBox="0 0 20 20" id="unlocked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V6h2v-.801C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8z"></path>
</symbol>
<symbol viewBox="0 0 20 20" id="locked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8zM12 8H8V5.199C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="close">
<path d="M14.348 14.849c-.469.469-1.229.469-1.697 0L10 11.819l-2.651 3.029c-.469.469-1.229.469-1.697 0-.469-.469-.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-.469-.469-.469-1.228 0-1.697.469-.469 1.228-.469 1.697 0L10 8.183l2.651-3.031c.469-.469 1.228-.469 1.697 0 .469.469.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c.469.469.469 1.229 0 1.698z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow">
<path d="M13.25 10L6.109 2.58c-.268-.27-.268-.707 0-.979.268-.27.701-.27.969 0l7.83 7.908c.268.271.268.709 0 .979l-7.83 7.908c-.268.271-.701.27-.969 0-.268-.269-.268-.707 0-.979L13.25 10z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow-down">
<path d="M17.418 6.109c.272-.268.709-.268.979 0s.271.701 0 .969l-7.908 7.83c-.27.268-.707.268-.979 0l-7.908-7.83c-.27-.268-.27-.701 0-.969.271-.268.709-.268.979 0L10 13.25l7.418-7.141z"/>
</symbol>
<symbol viewBox="0 0 24 24" id="jump-to">
<path d="M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.41L5.83 13H21V7z"/>
</symbol>
<symbol viewBox="0 0 24 24" id="expand">
<path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"/>
</symbol>
</defs>
</svg>
<div id="swagger-ui"></div>
<script src="./static/swagger-ui-bundle.js"> </script>
<script src="./static/swagger-ui-standalone-preset.js"> </script>
<script>
window.onload = function() {
// Build a system
const ui = SwaggerUIBundle({
url: "http://" + window.location.hostname + ":" + window.location.port + "/swagger/swagger.json",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
window.ui = ui
}
</script>
</body>
</html>
@@ -1,57 +0,0 @@
<!doctype html>
<html lang="en-US">
<body onload="run()">
</body>
</html>
<script>
'use strict';
function run () {
var oauth2 = window.opener.swaggerUIRedirectOauth2;
var sentState = oauth2.state;
var redirectUrl = oauth2.redirectUrl;
var isValid, qp, arr;
if (/code|token|error/.test(window.location.hash)) {
qp = window.location.hash.substring(1);
} else {
qp = location.search.substring(1);
}
arr = qp.split("&")
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})
qp = qp ? JSON.parse('{' + arr.join() + '}',
function (key, value) {
return key === "" ? value : decodeURIComponent(value)
}
) : {}
isValid = qp.state === sentState
if (oauth2.auth.schema.get("flow") === "accessCode" && !oauth2.auth.code) {
if (!isValid) {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "warning",
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
});
}
if (qp.code) {
delete oauth2.state;
oauth2.auth.code = qp.code;
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
} else {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "error",
message: "Authorization failed: no accessCode received from the server"
});
}
} else {
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
}
window.close();
}
</script>
@@ -1 +0,0 @@
{"version":3,"file":"swagger-ui-bundle.js","sources":["webpack:///swagger-ui-bundle.js"],"mappings":"AAAA;;;;;AAu7LA;;;;;;AA65DA;;;;;;;;;;;;;;;;;;;;;;;;;;AA68TA;;;;;;;;;;;;;;AAs8JA;;;;;;;;;AA69pBA;;;;;AA81QA;AAm4DA;;;;;;AAo4YA;;;;;;AA0iaA;AA4lvBA","sourceRoot":""}
@@ -1 +0,0 @@
{"version":3,"file":"swagger-ui-standalone-preset.js","sources":["webpack:///swagger-ui-standalone-preset.js"],"mappings":"AAAA;;;;;AA80CA;;;;;;AAqpFA","sourceRoot":""}
@@ -1 +0,0 @@
{"version":3,"file":"swagger-ui.css","sources":[],"mappings":"","sourceRoot":""}
@@ -1 +0,0 @@
{"version":3,"file":"swagger-ui.js","sources":["webpack:///swagger-ui.js"],"mappings":"AAAA;;;;;;AAokeA","sourceRoot":""}
Regular → Executable
+87 -34
View File
@@ -2,12 +2,63 @@ import time
from flask import request from flask import request
from flask_classy import route from flask_classy import route
from modules.core.core import cbpi from modules import DBModel, cbpi, get_db
from modules.core.db import get_db, DBModel
from modules.core.baseview import BaseView from modules.core.baseview import BaseView
from modules.database.dbmodel import Fermenter, FermenterStep
class Fermenter(DBModel):
__fields__ = ["name", "brewname", "sensor", "sensor2", "sensor3", "heater", "cooler", "logic", "config", "target_temp"]
__table_name__ = "fermenter"
__json_fields__ = ["config"]
class FermenterStep(DBModel):
__fields__ = ["name", "days", "hours", "minutes", "temp", "direction", "order", "state", "start", "end", "timer_start", "fermenter_id"]
__table_name__ = "fermenter_step"
@classmethod
def get_by_fermenter_id(cls, id):
cur = get_db().cursor()
cur.execute("SELECT * FROM %s WHERE fermenter_id = ?" % cls.__table_name__,(id,))
result = []
for r in cur.fetchall():
result.append(cls(r))
return result
@classmethod
def get_max_order(cls,id):
cur = get_db().cursor()
cur.execute("SELECT max(fermenter_step.'order') as 'order' FROM %s WHERE fermenter_id = ?" % cls.__table_name__, (id,))
r = cur.fetchone()
return r.get("order")
@classmethod
def update_state(cls, id, state):
cur = get_db().cursor()
cur.execute("UPDATE %s SET state = ? WHERE id =?" % cls.__table_name__, (state, id))
get_db().commit()
@classmethod
def update_timer(cls, id, timer):
cur = get_db().cursor()
cur.execute("UPDATE %s SET timer_start = ? WHERE id =?" % cls.__table_name__, (timer, id))
get_db().commit()
@classmethod
def get_by_state(cls, state):
cur = get_db().cursor()
cur.execute("SELECT * FROM %s WHERE state = ?" % cls.__table_name__, state)
r = cur.fetchone()
if r is not None:
return cls(r)
else:
return None
@classmethod
def reset_all_steps(cls,id):
cur = get_db().cursor()
cur.execute("UPDATE %s SET state = 'I', start = NULL, end = NULL, timer_start = NULL WHERE fermenter_id = ?" % cls.__table_name__, (id,))
get_db().commit()
class FermenterView(BaseView): class FermenterView(BaseView):
model = Fermenter model = Fermenter
cache_key = "fermenter" cache_key = "fermenter"
@@ -26,6 +77,7 @@ class FermenterView(BaseView):
def _post_put_callback(self, m): def _post_put_callback(self, m):
m.state = False m.state = False
self.reset(int(m.id))
@route('/<int:id>/targettemp/<temp>', methods=['POST']) @route('/<int:id>/targettemp/<temp>', methods=['POST'])
def postTargetTemp(self, id, temp): def postTargetTemp(self, id, temp):
@@ -35,7 +87,7 @@ class FermenterView(BaseView):
temp = float(temp) temp = float(temp)
cbpi.cache.get(self.cache_key)[id].target_temp = float(temp) cbpi.cache.get(self.cache_key)[id].target_temp = float(temp)
self.model.update(**self.api.cache.get(self.cache_key)[id].__dict__) self.model.update(**self.api.cache.get(self.cache_key)[id].__dict__)
cbpi.ws_emit("UPDATE_FERMENTER_TARGET_TEMP", {"id": id, "target_temp": temp}) cbpi.emit("UPDATE_FERMENTER_TARGET_TEMP", {"id": id, "target_temp": temp})
return ('', 204) return ('', 204)
@route('/<int:id>/brewname', methods=['POST']) @route('/<int:id>/brewname', methods=['POST'])
@@ -44,7 +96,7 @@ class FermenterView(BaseView):
brewname = data.get("brewname") brewname = data.get("brewname")
cbpi.cache.get(self.cache_key)[id].brewname = brewname cbpi.cache.get(self.cache_key)[id].brewname = brewname
self.model.update(**self.api.cache.get(self.cache_key)[id].__dict__) self.model.update(**self.api.cache.get(self.cache_key)[id].__dict__)
cbpi.ws_emit("UPDATE_FERMENTER_BREWNAME", {"id": id, "brewname": brewname}) cbpi.emit("UPDATE_FERMENTER_BREWNAME", {"id": id, "brewname": brewname})
return ('', 204) return ('', 204)
@classmethod @classmethod
@@ -68,7 +120,7 @@ class FermenterView(BaseView):
cbpi.cache.get(self.cache_key)[id].steps.append(f) cbpi.cache.get(self.cache_key)[id].steps.append(f)
cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id]) cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id])
return ('', 204) return ('', 204)
@@ -90,7 +142,7 @@ class FermenterView(BaseView):
FermenterStep.update(**s.__dict__) FermenterStep.update(**s.__dict__)
break break
cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id]) cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id])
return ('', 204) return ('', 204)
@route('/<int:id>/step/<int:stepid>', methods=["DELETE"]) @route('/<int:id>/step/<int:stepid>', methods=["DELETE"])
@@ -101,7 +153,7 @@ class FermenterView(BaseView):
del cbpi.cache.get(self.cache_key)[id].steps[idx] del cbpi.cache.get(self.cache_key)[id].steps[idx]
FermenterStep.delete(s.id) FermenterStep.delete(s.id)
break break
cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id]) cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id])
return ('', 204) return ('', 204)
@route('/<int:id>/start', methods=['POST']) @route('/<int:id>/start', methods=['POST'])
@@ -128,8 +180,7 @@ class FermenterView(BaseView):
if inactive is not None: if inactive is not None:
fermenter = self.get_fermenter(inactive.fermenter_id) fermenter = self.get_fermenter(inactive.fermenter_id)
current_temp = cbpi.get_sensor_value(int(fermenter.sensor))
current_temp = cbpi.sensor.get_value(int(fermenter.sensor))
inactive.state = 'A' inactive.state = 'A'
inactive.start = time.time() inactive.start = time.time()
@@ -140,7 +191,7 @@ class FermenterView(BaseView):
cbpi.cache["fermenter_task"][id] = inactive cbpi.cache["fermenter_task"][id] = inactive
cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id]) cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id])
return ('', 204) return ('', 204)
@route('/<int:id>/reset', methods=["POST"]) @route('/<int:id>/reset', methods=["POST"])
@@ -152,40 +203,40 @@ class FermenterView(BaseView):
if id in cbpi.cache["fermenter_task"]: if id in cbpi.cache["fermenter_task"]:
del cbpi.cache["fermenter_task"][id] del cbpi.cache["fermenter_task"][id]
cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id]) cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id])
return ('', 204) return ('', 204)
@route('/<int:id>/automatic', methods=['POST']) @route('/<int:id>/automatic', methods=['POST'])
def toggle(self, id): def toggle(self, id):
fermenter = cbpi.cache.get(self.cache_key)[id] fermenter = cbpi.cache.get(self.cache_key)[id]
try: try:
print fermenter.state
if fermenter.state is False: if fermenter.state is False:
# Start controller # Start controller
if fermenter.logic is not None: if fermenter.logic is not None:
cfg = fermenter.config.copy() cfg = fermenter.config.copy()
cfg.update( cfg.update(
dict(api=cbpi, fermenter_id=fermenter.id, heater=fermenter.heater, sensor=fermenter.sensor)) dict(api=cbpi, fermenter_id=fermenter.id, heater=fermenter.heater, sensor=fermenter.sensor))
instance = cbpi.fermentation.get_controller(fermenter.logic).get("class")(**cfg) instance = cbpi.get_fermentation_controller(fermenter.logic).get("class")(**cfg)
instance.init() instance.init()
fermenter.instance = instance fermenter.instance = instance
def run(instance): def run(instance):
instance.run() instance.run()
fermenter.state = not fermenter.state
cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key).get(id))
t = cbpi._socketio.start_background_task(target=run, instance=instance) t = cbpi.socketio.start_background_task(target=run, instance=instance)
fermenter.state = not fermenter.state fermenter.state = not fermenter.state
cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key).get(id)) cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key).get(id))
cbpi.emit("FERMENTER_CONTROLLER_STARTED", id=id)
else: else:
# Stop controller # Stop controller
fermenter.instance.stop() fermenter.instance.stop()
fermenter.state = not fermenter.state fermenter.state = not fermenter.state
cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key).get(id)) cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key).get(id))
cbpi.emit("FERMENTER_CONTROLLER_STOPPED", id=id)
except Exception as e: except Exception as e:
print e
cbpi.notify("Toogle Fementer Controller failed", "Pleae check the %s configuration" % fermenter.name, cbpi.notify("Toogle Fementer Controller failed", "Pleae check the %s configuration" % fermenter.name,
type="danger", timeout=None) type="danger", timeout=None)
return ('', 500) return ('', 500)
@@ -207,7 +258,7 @@ class FermenterView(BaseView):
step.timer_start = target_time step.timer_start = target_time
cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id]) cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id])
def check_step(self): def check_step(self):
for key, value in cbpi.cache["fermenter_task"].iteritems(): for key, value in cbpi.cache["fermenter_task"].iteritems():
@@ -234,33 +285,35 @@ class FermenterView(BaseView):
pass pass
@cbpi.addon.core.backgroundjob(key="read_target_temps_fermenter", interval=5) @cbpi.backgroundtask(key="read_target_temps_fermenter", interval=5)
def read_target_temps(cbpi): def read_target_temps(api):
""" """
background process that reads all passive sensors in interval of 1 second background process that reads all passive sensors in interval of 1 second
:return: None :return: None
""" """
result = {}
for key, value in cbpi.cache.get("fermenter").iteritems(): for key, value in cbpi.cache.get("fermenter").iteritems():
cbpi.sensor.write_log(key, value.target_temp, prefix="fermenter") cbpi.save_to_file(key, value.target_temp, prefix="fermenter")
instance = FermenterView() instance = FermenterView()
@cbpi.addon.core.backgroundjob(key="fermentation_task", interval=1) @cbpi.backgroundtask(key="fermentation_task", interval=1)
def execute_fermentation_step(cbpi): def execute_fermentation_step(api):
with cbpi._app.app_context(): with cbpi.app.app_context():
instance.check_step() instance.check_step()
def init_active_steps(): def init_active_steps():
pass '''
active_steps = FermenterStep.query.filter_by(state='A')
for a in active_steps:
db.session.expunge(a)
cbpi.cache["fermenter_task"][a.fermenter_id] = a
'''
@cbpi.initalizer(order=1)
@cbpi.addon.core.initializer(order=1)
def init(cbpi): def init(cbpi):
cbpi.cache["fermenter_task"] = {} FermenterView.register(cbpi.app, route_base='/api/fermenter')
FermenterView.register(cbpi._app, route_base='/api/fermenter')
FermenterView.init_cache() FermenterView.init_cache()
Executable → Regular
+96 -234
View File
@@ -1,234 +1,96 @@
from flask import request from flask import request
from flask_classy import FlaskView, route from flask_classy import FlaskView, route
from modules.core.core import cbpi from modules import cbpi, socketio
from modules.core.baseview import BaseView from modules.core.baseview import BaseView
from modules.core.db import DBModel from modules.core.db import DBModel
from modules.database.dbmodel import Kettle
class Kettle(DBModel):
class KettleView(BaseView): __fields__ = ["name","sensor", "heater", "automatic", "logic", "config", "agitator", "target_temp"]
model = Kettle __table_name__ = "kettle"
cache_key = "kettle" __json_fields__ = ["config"]
@route('/', methods=["GET"])
def getAll(self): class Kettle2View(BaseView):
""" model = Kettle
Get all Kettles cache_key = "kettle"
---
tags: @classmethod
- kettle def _pre_post_callback(self, data):
responses: data["target_temp"] = 0
200:
description: List auf all Kettles @classmethod
""" def post_init_callback(cls, obj):
return super(KettleView, self).getAll() obj.state = False
@route('/', methods=["POST"])
def post(self): def _post_post_callback(self, m):
""" m.state = False
Create a new kettle
--- def _pre_put_callback(self, m):
tags: try:
- kettle m.instance.stop()
parameters: except:
- in: body pass
name: body
schema: def _post_put_callback(self, m):
id: Kettle m.state = False
required:
- name @route('/<int:id>/targettemp/<temp>', methods=['POST'])
properties: def postTargetTemp(self, id, temp):
name: id = int(id)
type: string temp = float(temp)
description: name for user cbpi.cache.get("kettle")[id].target_temp = float(temp)
sensor: self.model.update(**self.api.cache.get(self.cache_key)[id].__dict__)
type: string cbpi.emit("UPDATE_KETTLE_TARGET_TEMP", {"id": id, "target_temp": temp})
description: name for user return ('', 204)
heater:
type: string @route('/<int:id>/automatic', methods=['POST'])
description: name for user def toggle(self, id):
automatic: kettle = cbpi.cache.get("kettle")[id]
type: string
description: name for user if kettle.state is False:
logic: # Start controller
type: string if kettle.logic is not None:
description: name for user cfg = kettle.config.copy()
config: cfg.update(dict(api=cbpi, kettle_id=kettle.id, heater=kettle.heater, sensor=kettle.sensor))
type: string instance = cbpi.get_controller(kettle.logic).get("class")(**cfg)
description: name for user instance.init()
agitator: kettle.instance = instance
type: string def run(instance):
description: name for user instance.run()
target_temp: t = self.api.socketio.start_background_task(target=run, instance=instance)
type: string kettle.state = not kettle.state
description: name for user cbpi.emit("UPDATE_KETTLE", cbpi.cache.get("kettle").get(id))
responses: else:
200: # Stop controller
description: User created kettle.instance.stop()
""" kettle.state = not kettle.state
return super(KettleView, self).post() cbpi.emit("UPDATE_KETTLE", cbpi.cache.get("kettle").get(id))
return ('', 204)
@route('/<int:id>', methods=["PUT"]) @cbpi.event("SET_TARGET_TEMP")
def put(self, id): def set_target_temp(id, temp):
""" '''
Update a kettle Change Taget Temp Event
--- :param id: kettle id
tags: :param temp: target temp to set
- kettle :return: None
parameters: '''
- in: path
name: id Kettle2View().postTargetTemp(id,temp)
schema:
type: integer @cbpi.backgroundtask(key="read_target_temps", interval=5)
required: true def read_target_temps(api):
description: Numeric ID of the Kettle """
- in: body background process that reads all passive sensors in interval of 1 second
name: body :return: None
schema: """
id: Kettle result = {}
required: for key, value in cbpi.cache.get("kettle").iteritems():
- name cbpi.save_to_file(key, value.target_temp, prefix="kettle")
properties:
name: @cbpi.initalizer()
type: string def init(cbpi):
description: name for user Kettle2View.api = cbpi
sensor: Kettle2View.register(cbpi.app,route_base='/api/kettle')
type: string Kettle2View.init_cache()
description: name for user
heater:
type: string
description: name for user
automatic:
type: string
description: name for user
logic:
type: string
description: name for user
config:
type: string
description: name for user
agitator:
type: string
description: name for user
target_temp:
type: string
description: name for user
responses:
200:
description: User created
"""
return super(KettleView, self).put(id)
@route('/<int:id>', methods=["DELETE"])
def delete(self, id):
"""
Delete a kettle
---
tags:
- kettle
parameters:
- in: path
name: id
schema:
type: integer
required: true
description: Numeric ID of the Kettle
responses:
200:
description: User created
"""
return super(KettleView, self).delete(id)
@classmethod
def _pre_post_callback(self, data):
data["target_temp"] = 0
@classmethod
def post_init_callback(cls, obj):
obj.state = False
def _post_post_callback(self, m):
m.state = False
def _pre_put_callback(self, m):
try:
m.instance.stop()
except:
pass
def _post_put_callback(self, m):
m.state = False
@route('/<int:id>/targettemp/<temp>', methods=['POST'])
def postTargetTemp(self, id, temp):
"""
Set Target Temp
---
tags:
- kettle
parameters:
- required: true
type: string
description: ID of pet to return
in: path
name: id
- required: true
type: string
description: Temperature you like to set
in: path
name: temp
responses:
201:
description: User created
"""
id = int(id)
temp = float(temp)
cbpi.brewing.set_target_temp(id, temp)
return ('', 204)
@route('/<int:id>/automatic', methods=['POST'])
def toggle(self, id):
"""
Set Target Temp
---
tags:
- kettle
parameters:
- required: true
type: string
description: ID of pet to return
in: path
name: id
- required: true
type: string
description: Temperature you like to set
in: path
name: temp
responses:
201:
description: User created
"""
self.api.brewing.toggle_automatic(id)
return ('', 204)
@cbpi.addon.core.backgroundjob(key="read_target_temps", interval=5)
def read_target_temps(api):
"""
background process that reads all passive sensors in interval of 1 second
:return: None
"""
result = {}
for key, value in cbpi.cache.get("kettle").iteritems():
cbpi.sensor.write_log(key, value.target_temp, prefix="kettle")
@cbpi.addon.core.initializer()
def init(cbpi):
KettleView.api = cbpi
KettleView.register(cbpi._app, route_base='/api/kettle')
KettleView.init_cache()
-56
View File
@@ -1,56 +0,0 @@
import flask_login
from flask import request
from modules.core.core import cbpi, addon
class User(flask_login.UserMixin):
pass
@addon.core.initializer(order=0)
def log(cbpi):
cbpi._login_manager = flask_login.LoginManager()
cbpi._login_manager.init_app(cbpi._app)
@cbpi._app.route('/login', methods=['POST'])
def login():
data = request.json
password = cbpi.get_config_parameter("password", None)
if password is None:
return ('',500)
if password == data.get("password",""):
user = User()
user.id = "craftbeerpi"
flask_login.login_user(user)
return ('',204)
else:
return ('',401)
@cbpi._app.route('/logout', methods=['POST'])
def logout():
flask_login.logout_user()
return 'Logged out'
@cbpi._login_manager.user_loader
def user_loader(user):
if cbpi.get_config_parameter("password_security", "NO") == "YES":
if user != "craftbeerpi":
return
user = User()
user.id = user
return user
else:
user = User()
user.id = user
return user
@cbpi._login_manager.unauthorized_handler
def unauthorized_handler():
return ('Please login',401)
Executable → Regular
+1 -175
View File
@@ -1,175 +1 @@
import datetime import endpoints
import os
from flask import Blueprint, request, send_from_directory, json
from flask_classy import FlaskView, route
from modules.core.core import cbpi
class LogView(FlaskView):
@route('/', methods=['GET'])
def get_all_logfiles(self):
"""
Get a list of all Log Files
---
tags:
- logs
responses:
200:
description: List of all log files
"""
result = []
for filename in os.listdir("./logs"):
if filename.endswith(".log"):
result.append(filename)
return json.dumps(result)
@route('/actions')
def actions(self):
"""
Get a list of all brewing actions
---
tags:
- logs
responses:
200:
description: List of all log files
"""
filename = "./logs/action.log"
if os.path.isfile(filename) == False:
return
import csv
array = []
with open(filename, 'rb') as f:
reader = csv.reader(f)
for row in reader:
try:
array.append([int((datetime.datetime.strptime(row[0], "%Y-%m-%d %H:%M:%S") - datetime.datetime(1970, 1, 1)).total_seconds()) * 1000, row[1]])
except:
pass
return json.dumps(array)
@route('/<file>', methods=["DELETE"])
def clearlog(self, file):
"""
Delete a log file by name
---
tags:
- logs
parameters:
- in: path
name: file
schema:
type: string
required: true
description: File name
responses:
204:
description: Log deleted
"""
if not self.check_filename(file):
return ('File Not Found', 404)
filename = "./logs/%s" % file
if os.path.isfile(filename) == True:
os.remove(filename)
cbpi.notify("log deleted succesfully", "")
return ('', 204)
else:
cbpi.notify("Failed to delete log", "", type="danger")
return ('', 404)
def read_log_as_json(self, type, id):
filename = "./logs/%s_%s.log" % (type, id)
if os.path.isfile(filename) == False:
return
import csv
array = []
with open(filename, 'rb') as f:
reader = csv.reader(f)
for row in reader:
try:
array.append([int((datetime.datetime.strptime(row[0], "%Y-%m-%d %H:%M:%S") - datetime.datetime(1970, 1, 1)).total_seconds()) * 1000, float(row[1])])
except:
pass
return array
def convert_chart_data_to_json(self, chart_data):
return {"name": chart_data["name"], "data": self.read_log_as_json(chart_data["data_type"], chart_data["data_id"])}
@route('/<t>/<int:id>', methods=["POST"])
def get_logs_as_json(self, t, id):
"""
Get Log as json
---
tags:
- logs
parameters:
- in: path
name: id
schema:
type: string
required: true
description: id of the file
responses:
200:
description: Log File Data
"""
data = request.json
result = []
if t == "s":
name = cbpi.cache.get("sensors").get(id).name
result.append({"name": name, "data": self.read_log_as_json("sensor", id)})
if t == "k":
kettle = cbpi.cache.get("kettle").get(id)
result = map(self.convert_chart_data_to_json, cbpi.brewing.get_controller(kettle.logic).get("class").chart(kettle))
if t == "f":
fermenter = cbpi.cache.get("fermenter").get(id)
result = map(self.convert_chart_data_to_json, cbpi.fermentation.get_controller(fermenter.logic).get("class").chart(fermenter))
return json.dumps(result)
@route('/download/<file>')
@cbpi.nocache
def download(self, file):
"""
Download a log file by name
---
tags:
- logs
parameters:
- in: path
name: file
schema:
type: string
required: true
description: filename
responses:
200:
description: Log file downloaded
"""
if not self.check_filename(file):
return ('File Not Found', 404)
return send_from_directory('../../logs', file, as_attachment=True, attachment_filename=file)
def check_filename(self, name):
import re
pattern = re.compile('^([A-Za-z0-9-_])+.log$')
return True if pattern.match(name) else False
@cbpi.addon.core.initializer()
def init(cbpi):
"""
Initializer for the message module
:param app: the flask app
:return: None
"""
LogView.register(cbpi._app, route_base='/api/logs')
+109
View File
@@ -0,0 +1,109 @@
import datetime
import os
from flask import Blueprint, request, send_from_directory, json
from flask_classy import FlaskView, route
from modules import cbpi
class LogView(FlaskView):
@route('/', methods=['GET'])
def get_all_logfiles(self):
result = []
for filename in os.listdir("./logs"):
if filename.endswith(".log"):
result.append(filename)
return json.dumps(result)
@route('/actions')
def actions(self):
filename = "./logs/action.log"
if os.path.isfile(filename) == False:
return
import csv
array = []
with open(filename, 'rb') as f:
reader = csv.reader(f)
for row in reader:
try:
array.append([int((datetime.datetime.strptime(row[0], "%Y-%m-%d %H:%M:%S") - datetime.datetime(1970, 1, 1)).total_seconds()) * 1000, row[1]])
except:
pass
return json.dumps(array)
@route('/<file>', methods=["DELETE"])
def clearlog(self, file):
"""
Overload delete method to shutdown sensor before delete
:param id: sensor id
:return: HTTP 204
"""
if not self.check_filename(file):
return ('File Not Found', 404)
filename = "./logs/%s" % file
if os.path.isfile(filename) == True:
os.remove(filename)
cbpi.notify("log deleted succesfully", "")
else:
cbpi.notify("Failed to delete log", "", type="danger")
return ('', 204)
def read_log_as_json(self, type, id):
filename = "./logs/%s_%s.log" % (type, id)
if os.path.isfile(filename) == False:
return
import csv
array = []
with open(filename, 'rb') as f:
reader = csv.reader(f)
for row in reader:
try:
array.append([int((datetime.datetime.strptime(row[0], "%Y-%m-%d %H:%M:%S") - datetime.datetime(1970, 1, 1)).total_seconds()) * 1000, float(row[1])])
except:
pass
return array
def convert_chart_data_to_json(self, chart_data):
return {"name": chart_data["name"], "data": self.read_log_as_json(chart_data["data_type"], chart_data["data_id"])}
@route('/<t>/<int:id>', methods=["POST"])
def get_logs_as_json(self, t, id):
data = request.json
result = []
if t == "s":
name = cbpi.cache.get("sensors").get(id).name
result.append({"name": name, "data": self.read_log_as_json("sensor", id)})
if t == "k":
kettle = cbpi.cache.get("kettle").get(id)
result = map(self.convert_chart_data_to_json, cbpi.get_controller(kettle.logic).get("class").chart(kettle))
if t == "f":
fermenter = cbpi.cache.get("fermenter").get(id)
result = map(self.convert_chart_data_to_json, cbpi.get_fermentation_controller(fermenter.logic).get("class").chart(fermenter))
return json.dumps(result)
@route('/download/<file>')
@cbpi.nocache
def download(self, file):
if not self.check_filename(file):
return ('File Not Found', 404)
return send_from_directory('../logs', file, as_attachment=True, attachment_filename=file)
def check_filename(self, name):
import re
pattern = re.compile('^([A-Za-z0-9-_])+.log$')
return True if pattern.match(name) else False
@cbpi.initalizer()
def init(app):
"""
Initializer for the message module
:param app: the flask app
:return: None
"""
LogView.register(cbpi.app, route_base='/api/logs')
+10 -26
View File
@@ -1,46 +1,30 @@
import json import json
from flask_classy import FlaskView, route from flask_classy import FlaskView, route
from modules.core.core import cbpi from modules import cbpi
class NotificationView(FlaskView): class NotificationView(FlaskView):
@route('/', methods=['GET']) @route('/', methods=['GET'])
def getMessages(self): def getMessages(self):
""" """
Get all Messages Get all messages
--- :return: current messages
tags:
- notification
responses:
200:
description: All messages
""" """
return json.dumps(cbpi.cache["messages"]) return json.dumps(cbpi.cache["messages"])
@route('/<id>', methods=['DELETE']) @route('/<id>', methods=['DELETE'])
def dismiss(self, id): def dismiss(self, id):
""" """
Delete Message Delete message from cache by id
--- :param id: message id to be deleted
tags: :return: empty response HTTP 204
- notification
parameters:
- in: path
name: id
schema:
type: integer
required: true
description: Numeric ID of the message
responses:
200:
description: Message deleted
""" """
for idx, m in enumerate(cbpi.cache.get("messages", [])): for idx, m in enumerate(cbpi.cache.get("messages", [])):
if (m.get("id") == id): if (m.get("id") == id):
cbpi.cache["messages"].pop(idx) cbpi.cache["messages"].pop(idx)
return ('', 204) return ('', 204)
#@cbpi.event("MESSAGE", async=True) @cbpi.event("MESSAGE", async=True)
def messageEvent(message, **kwargs): def messageEvent(message, **kwargs):
""" """
React on message event. add the message to the cache and push the message to the clients React on message event. add the message to the cache and push the message to the clients
@@ -52,7 +36,7 @@ def messageEvent(message, **kwargs):
cbpi.cache["messages"].append(message) cbpi.cache["messages"].append(message)
cbpi.emit("NOTIFY", message) cbpi.emit("NOTIFY", message)
@cbpi.addon.core.initializer(order=2) @cbpi.initalizer(order=2)
def init(cbpi): def init(cbpi):
""" """
Initializer for the message module Initializer for the message module
@@ -63,4 +47,4 @@ def init(cbpi):
msg = {"id": len(cbpi.cache["messages"]), "type": "info", "headline": "Support CraftBeerPi with your donation", "message": "You will find the PayPay Donation button in the system menu" , "read": False} msg = {"id": len(cbpi.cache["messages"]), "type": "info", "headline": "Support CraftBeerPi with your donation", "message": "You will find the PayPay Donation button in the system menu" , "read": False}
cbpi.cache["messages"].append(msg) cbpi.cache["messages"].append(msg)
NotificationView.register(cbpi._app, route_base='/api/notification') NotificationView.register(cbpi.app, route_base='/api/notification')
-138
View File
@@ -1,138 +0,0 @@
import sys
from flask import request, send_from_directory, json
from importlib import import_module
from modules.core.core import cbpi
from git import Repo
import os
import requests
import yaml
import shutil
from flask_classy import FlaskView, route
modules = {}
class PluginView(FlaskView):
def merge(self, source, destination):
"""
Helper method to merge two dicts
:param source:
:param destination:
:return:
"""
for key, value in source.items():
if isinstance(value, dict):
# get node or create one
node = destination.setdefault(key, {})
self.merge(value, node)
else:
destination[key] = value
return destination
@route('/', methods=['GET'])
def get(self):
"""
Get Plugin List
---
tags:
- plugin
responses:
200:
description: List of all plugins
"""
response = requests.get("https://raw.githubusercontent.com/Manuel83/craftbeerpi-plugins/master/plugins.yaml")
self.api.cache["plugins"] = self.merge(yaml.load(response.text), self.api.cache["plugins"])
for key, value in cbpi.cache["plugins"].iteritems():
value["installed"] = os.path.isdir("./plugins/%s/" % (key))
return json.dumps(cbpi.cache["plugins"])
@route('/<name>', methods=['DELETE'])
def delete(self,name):
"""
Delete Plugin
---
tags:
- plugin
parameters:
- in: path
name: name
schema:
type: string
required: true
description: Plugin name
responses:
200:
description: Plugin deleted
"""
if os.path.isdir("./plugins/"+name) is False:
return ('Dir not found', 500)
shutil.rmtree("./plugins/"+name)
cbpi.notify("Plugin deleted", "Plugin %s deleted successfully" % name)
return ('', 204)
@route('/<name>/download', methods=['POST'])
def download(self, name):
"""
Download Plugin
---
tags:
- plugin
parameters:
- in: path
name: name
schema:
type: string
required: true
description: Plugin name
responses:
200:
description: Plugin downloaded
"""
plugin = self.api.cache["plugins"].get(name)
plugin["loading"] = True
if plugin is None:
return ('', 404)
try:
Repo.clone_from(plugin.get("repo_url"), "./modules/plugins/%s/" % (name))
self.api.notify("Download successful", "Plugin %s downloaded successfully" % name)
finally:
plugin["loading"] = False
return ('', 204)
@route('/<name>/update', methods=['POST'])
def update(self, name):
"""
Pull Plugin Update
---
tags:
- plugin
parameters:
- in: path
name: name
schema:
type: string
required: true
description: Plugin name
responses:
200:
description: Plugin updated
"""
repo = Repo("./modules/plugins/%s/" % (name))
o = repo.remotes.origin
info = o.pull()
self.api.notify("Plugin Updated", "Plugin %s updated successfully. Please restart the system" % name)
return ('', 204)
@cbpi.addon.core.initializer()
def init(cbpi):
cbpi.cache["plugins"] = {}
PluginView.api = cbpi
PluginView.register(cbpi._app, route_base='/api/plugin')
+52 -47
View File
@@ -2,28 +2,18 @@ from flask import json, request
from flask_classy import FlaskView, route from flask_classy import FlaskView, route
from git import Repo, Git from git import Repo, Git
import sqlite3 import sqlite3
from modules.core.core import cbpi from modules.app_config import cbpi
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
import pprint import pprint
import time import time
import os import os
from modules.step import Step,StepView from modules.steps import Step,StepView
import xml.etree.ElementTree import xml.etree.ElementTree
class BeerXMLImport(FlaskView): class BeerXMLImport(FlaskView):
BEER_XML_FILE = "./upload/beer.xml" BEER_XML_FILE = "./upload/beer.xml"
@route('/', methods=['GET']) @route('/', methods=['GET'])
def get(self): def get(self):
"""
Get BeerXML
---
tags:
- beerxml
responses:
200:
description: BeerXML file stored in CraftBeerPI
"""
if not os.path.exists(self.BEER_XML_FILE): if not os.path.exists(self.BEER_XML_FILE):
self.api.notify(headline="File Not Found", message="Please upload a Beer.xml File", self.api.notify(headline="File Not Found", message="Please upload a Beer.xml File",
type="danger") type="danger")
@@ -41,16 +31,6 @@ class BeerXMLImport(FlaskView):
@route('/upload', methods=['POST']) @route('/upload', methods=['POST'])
def upload_file(self): def upload_file(self):
"""
Upload BeerXML File
---
tags:
- beerxml
responses:
200:
description: BeerXML File Uploaded
"""
try: try:
if request.method == 'POST': if request.method == 'POST':
file = request.files['file'] file = request.files['file']
@@ -66,25 +46,9 @@ class BeerXMLImport(FlaskView):
@route('/<int:id>', methods=['POST']) @route('/<int:id>', methods=['POST'])
def load(self, id): def load(self, id):
"""
Load Recipe from BeerXML
---
tags:
- beerxml
parameters:
- in: path
name: id
schema:
type: integer
required: true
description: Recipe ID from BeerXML
responses:
200:
description: Recipe loaed
"""
steps = self.getSteps(id) steps = self.getSteps(id)
boil_time_alerts = self.getBoilAlerts(id)
name = self.getRecipeName(id) name = self.getRecipeName(id)
self.api.set_config_parameter("brew_name", name) self.api.set_config_parameter("brew_name", name)
boil_time = self.getBoilTime(id) boil_time = self.getBoilTime(id)
@@ -104,10 +68,29 @@ class BeerXMLImport(FlaskView):
for row in steps: for row in steps:
Step.insert(**{"name": row.get("name"), "type": mashstep_type, "config": {"kettle": mash_kettle, "temp": float(row.get("temp")), "timer": row.get("timer")}}) Step.insert(**{"name": row.get("name"), "type": mashstep_type, "config": {"kettle": mash_kettle, "temp": float(row.get("temp")), "timer": row.get("timer")}})
Step.insert(**{"name": "ChilStep", "type": "ChilStep", "config": {"timer": 15}}) Step.insert(**{"name": "ChilStep", "type": "ChilStep", "config": {"timer": 15}})
## Add cooking step ## Add boiling step
Step.insert(**{"name": "Boil", "type": boilstep_type, "config": {"kettle": boil_kettle, "temp": boil_temp, "timer": boil_time}}) Step.insert(**{
"name": "Boil",
"type": boilstep_type,
"config": {
"kettle": boil_kettle,
"temp": boil_temp,
"timer": boil_time,
## Beer XML defines additions as the total time spent in boiling,
## CBP defines it as time-until-alert
## Also, The model supports five boil-time additions.
## Set the rest to None to signal them being absent
"hop_1": boil_time - boil_time_alerts[0] if len(boil_time_alerts) >= 1 else None,
"hop_2": boil_time - boil_time_alerts[1] if len(boil_time_alerts) >= 2 else None,
"hop_3": boil_time - boil_time_alerts[2] if len(boil_time_alerts) >= 3 else None,
"hop_4": boil_time - boil_time_alerts[3] if len(boil_time_alerts) >= 4 else None,
"hop_5": boil_time - boil_time_alerts[4] if len(boil_time_alerts) >= 5 else None
}
})
## Add Whirlpool step ## Add Whirlpool step
Step.insert(**{"name": "Whirlpool", "type": "ChilStep", "config": {"timer": 15}}) Step.insert(**{"name": "Whirlpool", "type": "ChilStep", "config": {"timer": 15}})
StepView().reset()
self.api.emit("UPDATE_ALL_STEPS", Step.get_all()) self.api.emit("UPDATE_ALL_STEPS", Step.get_all())
self.api.notify(headline="Recipe %s loaded successfully" % name, message="") self.api.notify(headline="Recipe %s loaded successfully" % name, message="")
except Exception as e: except Exception as e:
@@ -124,24 +107,46 @@ class BeerXMLImport(FlaskView):
e = xml.etree.ElementTree.parse(self.BEER_XML_FILE).getroot() e = xml.etree.ElementTree.parse(self.BEER_XML_FILE).getroot()
return float(e.find('./RECIPE[%s]/BOIL_TIME' % (str(id))).text) return float(e.find('./RECIPE[%s]/BOIL_TIME' % (str(id))).text)
def getBoilAlerts(self, id):
e = xml.etree.ElementTree.parse(self.BEER_XML_FILE).getroot()
recipe = e.find('./RECIPE[%s]' % (str(id)))
alerts = []
for e in recipe.findall('./HOPS/HOP'):
use = e.find('USE').text
## Hops which are not used in the boil step should not cause alerts
if use != 'Aroma' and use != 'Boil':
continue
alerts.append(float(e.find('TIME').text))
## There might also be miscelaneous additions during boild time
for e in recipe.findall('MISCS/MISC[USE="Boil"]'):
alerts.append(float(e.find('TIME').text))
## Dedupe and order the additions by their time, to prevent multiple alerts at the same time
alerts = sorted(list(set(alerts)))
## CBP should have these additions in reverse
alerts.reverse()
return alerts
def getSteps(self, id): def getSteps(self, id):
e = xml.etree.ElementTree.parse(self.BEER_XML_FILE).getroot() e = xml.etree.ElementTree.parse(self.BEER_XML_FILE).getroot()
steps = [] steps = []
for e in e.findall('./RECIPE[%s]/MASH/MASH_STEPS/MASH_STEP' % (str(id))): for e in e.findall('./RECIPE[%s]/MASH/MASH_STEPS/MASH_STEP' % (str(id))):
if self.api.get_config_parameter("unit", "C") == "C": if self.api.get_config_parameter("unit", "C") == "C":
temp = float(e.find("STEP_TEMP").text) temp = float(e.find("STEP_TEMP").text)
else: else:
temp = round(9.0 / 5.0 * float(e.find("STEP_TEMP").text) + 32, 2) temp = round(9.0 / 5.0 * float(e.find("STEP_TEMP").text) + 32, 2)
steps.append({"name": e.find("NAME").text, "temp": temp, "timer": float(e.find("STEP_TIME").text)}) steps.append({"name": e.find("NAME").text, "temp": temp, "timer": float(e.find("STEP_TIME").text)})
return steps return steps
@cbpi.addon.core.initializer() @cbpi.initalizer()
def init(cbpi): def init(cbpi):
BeerXMLImport.api = cbpi BeerXMLImport.api = cbpi
BeerXMLImport.register(cbpi._app, route_base='/api/beerxml') BeerXMLImport.register(cbpi.app, route_base='/api/beerxml')
+4 -41
View File
@@ -2,28 +2,18 @@ from flask import json, request
from flask_classy import FlaskView, route from flask_classy import FlaskView, route
from git import Repo, Git from git import Repo, Git
import sqlite3 import sqlite3
from modules.core.core import cbpi from modules.app_config import cbpi
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
import pprint import pprint
import time import time
import os import os
from modules.step import Step, StepView from modules.steps import Step, StepView
class KBH(FlaskView): class KBH(FlaskView):
@route('/', methods=['GET']) @route('/', methods=['GET'])
def get(self): def get(self):
"""
Get all recipes from uploaded kleinerbrauhelfer database
---
tags:
- kleinerbrauhelfer
responses:
200:
description: Recipes from kleinerbrauhelfer database
"""
conn = None conn = None
try: try:
if not os.path.exists(self.api.app.config['UPLOAD_FOLDER'] + '/kbh.db'): if not os.path.exists(self.api.app.config['UPLOAD_FOLDER'] + '/kbh.db'):
@@ -51,16 +41,6 @@ class KBH(FlaskView):
@route('/upload', methods=['POST']) @route('/upload', methods=['POST'])
def upload_file(self): def upload_file(self):
"""
Upload KleinerBrauhelfer Database File
---
tags:
- kleinerbrauhelfer
responses:
200:
description: File uploaed
"""
try: try:
if request.method == 'POST': if request.method == 'POST':
file = request.files['file'] file = request.files['file']
@@ -77,23 +57,6 @@ class KBH(FlaskView):
@route('/<int:id>', methods=['POST']) @route('/<int:id>', methods=['POST'])
def load(self, id): def load(self, id):
"""
Load Recipe from Kleinerbrauhelfer Database
---
tags:
- kleinerbrauhelfer
parameters:
- in: path
name: id
schema:
type: integer
required: true
description: Numeric ID of recipe
responses:
200:
description: Recipe loaded
"""
mashstep_type = cbpi.get_config_parameter("step_mash", "MashStep") mashstep_type = cbpi.get_config_parameter("step_mash", "MashStep")
mashinstep_type = cbpi.get_config_parameter("step_mashin", "MashInStep") mashinstep_type = cbpi.get_config_parameter("step_mashin", "MashInStep")
chilstep_type = cbpi.get_config_parameter("step_chil", "ChilStep") chilstep_type = cbpi.get_config_parameter("step_chil", "ChilStep")
@@ -139,8 +102,8 @@ class KBH(FlaskView):
@cbpi.addon.core.initializer() @cbpi.initalizer()
def init(cbpi): def init(cbpi):
KBH.api = cbpi KBH.api = cbpi
KBH.register(cbpi._app, route_base='/api/kbh') KBH.register(cbpi.app, route_base='/api/kbh')
+4 -4
View File
@@ -2,12 +2,12 @@ from flask import json, request
from flask_classy import FlaskView, route from flask_classy import FlaskView, route
from git import Repo, Git from git import Repo, Git
import sqlite3 import sqlite3
from modules.core.core import cbpi from modules.app_config import cbpi
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
import pprint import pprint
import time import time
import os import os
from modules.step import Step,StepView from modules.steps import Step,StepView
import xml.etree.ElementTree import xml.etree.ElementTree
@@ -56,7 +56,7 @@ class RESTImport(FlaskView):
return ('', 204) return ('', 204)
@cbpi.addon.core.initializer() @cbpi.initalizer()
def init(cbpi): def init(cbpi):
RESTImport.api = cbpi RESTImport.api = cbpi
RESTImport.register(cbpi._app, route_base='/api/recipe/import/v1') RESTImport.register(cbpi.app, route_base='/api/recipe/import/v1')
-67
View File
@@ -1,67 +0,0 @@
import time
from flask_classy import route
from modules.core.core import cbpi
from modules.core.db import DBModel
from modules.core.baseview import BaseView
from modules.database.dbmodel import Sensor
class SensorView(BaseView):
model = Sensor
cache_key = "sensors"
@route('<int:id>/action/<method>', methods=["POST"])
def action(self, id, method):
"""
Sensor Action
---
tags:
- sensor
parameters:
- in: path
name: id
schema:
type: integer
required: true
description: Numeric ID of the sensor
- in: path
name: method
schema:
type: string
required: true
description: action method name
responses:
200:
description: Sensor Action called
"""
cbpi.sensor.action(id, method)
return ('', 204)
def _post_post_callback(self, m):
cbpi.sensor.init_one(m.id)
def _post_put_callback(self, m):
cbpi.sensor.stop_one(m.id)
cbpi.sensor.init_one(m.id)
def _pre_delete_callback(self, m):
cbpi.sensor.stop_one(m.id)
@cbpi.addon.core.initializer(order=1000)
def init(cbpi):
SensorView.register(cbpi._app, route_base='/api/sensor')
SensorView.init_cache()
#@cbpi.backgroundtask(key="read_passiv_sensor", interval=5)
def read_passive_sensor(api):
"""
background process that reads all passive sensors in interval of 1 second
:return: None
"""
for key, value in cbpi.cache.get("sensors").iteritems():
if value.mode == "P":
value.instance.read()
+48
View File
@@ -0,0 +1,48 @@
import time
from flask_classy import route
from modules import DBModel, cbpi
from modules.core.baseview import BaseView
class Sensor(DBModel):
__fields__ = ["name","type", "config", "hide"]
__table_name__ = "sensor"
__json_fields__ = ["config"]
class SensorView(BaseView):
model = Sensor
cache_key = "sensors"
@route('<int:id>/action/<method>', methods=["POST"])
def action(self, id, method):
cbpi.cache.get("sensors").get(id).instance.__getattribute__(method)()
return ('', 204)
def _post_post_callback(self, m):
cbpi.init_sensor(m.id)
def _post_put_callback(self, m):
cbpi.stop_sensor(m.id)
cbpi.init_sensor(m.id)
def _pre_delete_callback(self, m):
cbpi.stop_sensor(m.id)
@cbpi.initalizer(order=1000)
def init(cbpi):
SensorView.register(cbpi.app, route_base='/api/sensor')
SensorView.init_cache()
cbpi.init_sensors()
@cbpi.backgroundtask(key="read_passiv_sensor", interval=5)
def read_passive_sensor(api):
"""
background process that reads all passive sensors in interval of 1 second
:return: None
"""
for key, value in cbpi.cache.get("sensors").iteritems():
if value.mode == "P":
value.instance.read()
Executable → Regular
+36 -35
View File
@@ -1,35 +1,36 @@
from modules import cbpi from modules import cbpi
def getserial(): def getserial():
cpuserial = "0000000000000000" cpuserial = "0000000000000000"
try: try:
f = open('/proc/cpuinfo','r') f = open('/proc/cpuinfo','r')
for line in f: for line in f:
if line[0:6]=='Serial': if line[0:6]=='Serial':
cpuserial = line[10:26] cpuserial = line[10:26]
f.close() f.close()
except: except:
cpuserial = "0000000000000000" cpuserial = "0000000000000000"
return cpuserial return cpuserial
@cbpi.initalizer(order=9999) @cbpi.initalizer(order=9999)
def sendStats(cbpi): def sendStats(cbpi):
try: try:
serial = getserial() serial = getserial()
info = {
"id": serial, info = {
"version": "3.1", "id": serial,
"kettle": len(cbpi.cache.get("kettle")), "version": "3.0",
"hardware": len(cbpi.cache.get("actors")), "kettle": len(cbpi.cache.get("kettle")),
"thermometer": "CBP3.0", "hardware": len(cbpi.cache.get("actors")),
"hardware_control": "CBP3.0" "thermometer": "CBP3.0",
} "hardware_control": "CBP3.0"
}
import requests
#r = requests.post('http://statistics.craftbeerpi.com', json=info) import requests
r = requests.post('http://statistics.craftbeerpi.com', json=info)
except Exception as e:
pass except Exception as e:
pass
@@ -1,244 +1,239 @@
import time import time
from flask import json, request from flask import json, request
from flask_classy import route from flask_classy import route
from modules.core.db import DBModel from modules import DBModel, cbpi, get_db
from modules.core.baseview import BaseView from modules.core.baseview import BaseView
from modules.core.core import cbpi
from modules.database.dbmodel import Step
class Step(DBModel):
__fields__ = ["name","type", "stepstate", "state", "start", "end", "order", "config"]
class StepView(BaseView): __table_name__ = "step"
model = Step __json_fields__ = ["config", "stepstate"]
def _pre_post_callback(self, data): __order_by__ = "order"
order = self.model.get_max_order() __as_array__ = True
data["order"] = 1 if order is None else order + 1
data["state"] = "I" @classmethod
def get_max_order(cls):
@route('/sort', methods=["POST"]) cur = get_db().cursor()
def sort_steps(self): cur.execute("SELECT max(step.'order') as 'order' FROM %s" % cls.__table_name__)
""" r = cur.fetchone()
Sort all steps return r.get("order")
---
tags: @classmethod
- steps def get_by_state(cls, state, order=True):
responses: cur = get_db().cursor()
204: cur.execute("SELECT * FROM %s WHERE state = ? ORDER BY %s.'order'" % (cls.__table_name__,cls.__table_name__,), state)
description: Steps sorted. Update delivered via web socket r = cur.fetchone()
""" if r is not None:
Step.sort(request.json) return cls(r)
cbpi.ws_emit("UPDATE_ALL_STEPS", self.model.get_all()) else:
return ('', 204) return None
@route('/', methods=["DELETE"]) @classmethod
def deleteAll(self): def delete_all(cls):
""" cur = get_db().cursor()
Delete all Steps cur.execute("DELETE FROM %s" % cls.__table_name__)
--- get_db().commit()
tags:
- steps @classmethod
responses: def reset_all_steps(cls):
204: cur = get_db().cursor()
description: All steps deleted cur.execute("UPDATE %s SET state = 'I', stepstate = NULL , start = NULL, end = NULL " % cls.__table_name__)
""" get_db().commit()
self.model.delete_all()
self.api.emit("ALL_BREWING_STEPS_DELETED") @classmethod
cbpi.ws_emit("UPDATE_ALL_STEPS", self.model.get_all()) def update_state(cls, id, state):
return ('', 204) cur = get_db().cursor()
cur.execute("UPDATE %s SET state = ? WHERE id =?" % cls.__table_name__, (state, id))
@route('/action/<method>', methods=["POST"]) get_db().commit()
def action(self, method):
""" @classmethod
Call Step Action def update_step_state(cls, id, state):
--- cur = get_db().cursor()
tags: cur.execute("UPDATE %s SET stepstate = ? WHERE id =?" % cls.__table_name__, (json.dumps(state),id))
- steps get_db().commit()
responses:
204: @classmethod
description: Step action called def sort(cls, new_order):
""" cur = get_db().cursor()
self.api.emit("BREWING_STEP_ACTION_INVOKED", method=method)
cbpi.cache["active_step"].__getattribute__(method)() for e in new_order:
return ('', 204)
cur.execute("UPDATE %s SET '%s' = ? WHERE id = ?" % (cls.__table_name__, "order"), (e[1], e[0]))
@route('/reset', methods=["POST"]) get_db().commit()
def reset(self):
""" class StepView(BaseView):
Reset All Steps model = Step
--- def _pre_post_callback(self, data):
tags: order = self.model.get_max_order()
- steps data["order"] = 1 if order is None else order + 1
responses: data["state"] = "I"
200:
description: Steps reseted @route('/sort', methods=["POST"])
""" def sort_steps(self):
self.model.reset_all_steps() Step.sort(request.json)
self.stop_step() cbpi.emit("UPDATE_ALL_STEPS", self.model.get_all())
self.api.emit("ALL_BREWING_STEPS_RESET") return ('', 204)
cbpi.ws_emit("UPDATE_ALL_STEPS", self.model.get_all())
return ('', 204) @route('/', methods=["DELETE"])
def deleteAll(self):
def stop_step(self): self.model.delete_all()
''' cbpi.emit("UPDATE_ALL_STEPS", self.model.get_all())
stop active step return ('', 204)
:return:
''' @route('/action/<method>', methods=["POST"])
step = cbpi.cache.get("active_step") def action(self, method):
cbpi.cache["active_step"] = None cbpi.cache["active_step"].__getattribute__(method)()
self.api.emit("BREWING_STEPS_STOP") return ('', 204)
if step is not None:
step.finish() @route('/reset', methods=["POST"])
def reset(self):
@route('/reset/current', methods=['POST']) self.model.reset_all_steps()
def resetCurrentStep(self): self.stop_step()
""" cbpi.emit("UPDATE_ALL_STEPS", self.model.get_all())
Reset current Steps return ('', 204)
---
tags: def stop_step(self):
- steps '''
responses: stop active step
200: :return:
description: Current Steps reseted '''
""" step = cbpi.cache.get("active_step")
step = cbpi.cache.get("active_step") cbpi.cache["active_step"] = None
if step is not None: if step is not None:
step.reset() step.finish()
if step.is_dirty():
@route('/reset/current', methods=['POST'])
state = {} def resetCurrentStep(self):
for field in step.managed_fields: '''
state[field] = step.__getattribute__(field) Reset current step
Step.update_step_state(step.id, state) :return:
step.reset_dirty() '''
self.api.emit("BREWING_STEPS_RESET_CURRENT") step = cbpi.cache.get("active_step")
cbpi.ws_emit("UPDATE_ALL_STEPS", self.model.get_all())
return ('', 204) if step is not None:
step.reset()
def init_step(self, step): if step.is_dirty():
cbpi.brewing.log_action("Start Step %s" % step.name) state = {}
type_cfg = cbpi.cache.get("step_types").get(step.type) for field in step.managed_fields:
if type_cfg is None: state[field] = step.__getattribute__(field)
# if type not found Step.update_step_state(step.id, state)
return step.reset_dirty()
cbpi.emit("UPDATE_ALL_STEPS", self.model.get_all())
# copy config to stepstate return ('', 204)
# init step
cfg = step.config.copy() def init_step(self, step):
cfg.update(dict(name=step.name, api=cbpi, id=step.id, timer_end=None, managed_fields=get_manged_fields_as_array(type_cfg))) cbpi.log_action("Start Step %s" % step.name)
instance = type_cfg.get("class")(**cfg) type_cfg = cbpi.cache.get("step_types").get(step.type)
instance.init() if type_cfg is None:
# set step instance to ache # if type not found
cbpi.cache["active_step"] = instance return
@route('/next', methods=['POST']) # copy config to stepstate
@route('/start', methods=['POST']) # init step
def start(self): cfg = step.config.copy()
cfg.update(dict(name=step.name, api=cbpi, id=step.id, timer_end=None, managed_fields=get_manged_fields_as_array(type_cfg)))
""" instance = type_cfg.get("class")(**cfg)
Next Step instance.init()
--- # set step instance to ache
tags: cbpi.cache["active_step"] = instance
- steps
responses: @route('/next', methods=['POST'])
200: @route('/start', methods=['POST'])
description: Next Step def start(self):
""" active = Step.get_by_state("A")
active = Step.get_by_state("A") inactive = Step.get_by_state('I')
inactive = Step.get_by_state('I')
if (active is not None):
if (active is not None): active.state = 'D'
active.state = 'D' active.end = int(time.time())
active.end = int(time.time()) self.stop_step()
self.stop_step() Step.update(**active.__dict__)
Step.update(**active.__dict__)
self.api.emit("BREWING_STEP_DONE") if (inactive is not None):
self.init_step(inactive)
if (inactive is not None): inactive.state = 'A'
self.init_step(inactive) inactive.stepstate = inactive.config
inactive.state = 'A' inactive.start = int(time.time())
inactive.stepstate = inactive.config Step.update(**inactive.__dict__)
inactive.start = int(time.time()) else:
Step.update(**inactive.__dict__) cbpi.log_action("Brewing Finished")
self.api.emit("BREWING_STEP_STARTED") cbpi.notify("Brewing Finished", "You are done!", timeout=None)
else:
cbpi.brewing.log_action("Brewing Finished") cbpi.emit("UPDATE_ALL_STEPS", Step.get_all())
self.api.emit("BREWING_FINISHED") return ('', 204)
cbpi.notify("Brewing Finished", "You are done!", timeout=None)
def get_manged_fields_as_array(type_cfg):
cbpi.ws_emit("UPDATE_ALL_STEPS", Step.get_all()) result = []
for f in type_cfg.get("properties"):
return ('', 204)
result.append(f.get("name"))
def get_manged_fields_as_array(type_cfg):
return result
result = []
for f in type_cfg.get("properties"): @cbpi.try_catch(None)
def init_after_startup():
result.append(f.get("name")) '''
Restart after startup. Check is a step is in state A and reinitialize
return result :return: None
'''
def init_after_startup(): step = Step.get_by_state('A')
''' # We have an active step
Restart after startup. Check is a step is in state A and reinitialize if step is not None:
:return: None
''' # get the type
step = Step.get_by_state('A')
# We have an active step
if step is not None: type_cfg = cbpi.cache.get("step_types").get(step.type)
# get the type
type_cfg = cbpi.cache.get("step_types").get(step.type) if type_cfg is None:
# step type not found. cant restart step
if type_cfg is None: return
# step type not found. cant restart step
return cfg = step.stepstate.copy()
cfg = step.stepstate.copy() cfg.update(dict(api=cbpi, id=step.id, managed_fields=get_manged_fields_as_array(type_cfg)))
cfg.update(dict(api=cbpi, id=step.id, managed_fields=get_manged_fields_as_array(type_cfg))) instance = type_cfg.get("class")(**cfg)
instance = type_cfg.get("class")(**cfg) instance.init()
instance.init() cbpi.cache["active_step"] = instance
cbpi.cache["active_step"] = instance
@cbpi.initalizer(order=2000)
@cbpi.addon.core.initializer(order=2000) def init(cbpi):
def init(cbpi):
StepView.register(cbpi.app, route_base='/api/step')
StepView.register(cbpi._app, route_base='/api/step')
def get_all():
def get_all(): with cbpi.app.app_context():
with cbpi._app.app_context(): return Step.get_all()
return Step.get_all()
with cbpi.app.app_context():
with cbpi._app.app_context(): init_after_startup()
init_after_startup() cbpi.add_cache_callback("steps", get_all)
cbpi.add_cache_callback("steps", get_all) @cbpi.backgroundtask(key="step_task", interval=0.1)
def execute_step(api):
@cbpi.addon.core.backgroundjob(key="step_task", interval=0.1) '''
def execute_step(api): Background job which executes the step
''' :return:
Background job which executes the step '''
:return: with cbpi.app.app_context():
''' step = cbpi.cache.get("active_step")
with cbpi._app.app_context(): if step is not None:
step = cbpi.cache.get("active_step") step.execute()
if step is not None: if step.is_dirty():
step.execute() state = {}
for field in step.managed_fields:
if step.is_dirty(): state[field] = step.__getattribute__(field)
Step.update_step_state(step.id, state)
state = {} step.reset_dirty()
for field in step.managed_fields: cbpi.emit("UPDATE_ALL_STEPS", Step.get_all())
state[field] = step.__getattribute__(field)
if step.n is True:
Step.update_step_state(step.id, state)
step.reset_dirty() StepView().start()
cbpi.ws_emit("UPDATE_ALL_STEPS", Step.get_all()) cbpi.emit("UPDATE_ALL_STEPS", Step.get_all())
if step.n is True:
StepView().start()
cbpi.ws_emit("UPDATE_ALL_STEPS", Step.get_all())
+1 -193
View File
@@ -1,193 +1 @@
import flask_login import endpoints
import requests
import yaml
from flask import json, url_for, Response, request
from flask_classy import FlaskView, route
from flask_login import login_required, current_user
from git import Repo, Git
from modules.core.core import cbpi
import pprint
import time
from modules.login import User
class SystemView(FlaskView):
def doShutdown(self):
time.sleep(5)
from subprocess import call
call("halt")
@login_required
@route('/shutdown', methods=['POST'])
def shutdown(self):
"""
System Shutdown
---
tags:
- system
responses:
200:
description: Shutdown triggered
"""
self.doShutdown()
return ('', 204)
def doReboot(self):
time.sleep(5)
from subprocess import call
call("reboot")
@login_required
@route('/reboot', methods=['POST'])
def reboot(self):
"""
System Reboot
---
tags:
- system
responses:
200:
description: Reboot triggered
"""
self.doReboot()
return ('', 204)
@login_required
@route('/checkout', methods=['POST'])
def checkout_tag(self):
data = request.json
name = data.get("name")
if name is None:
return ('', 500)
repo = Repo('./')
repo.git.reset('--hard')
o = repo.remotes.origin
o.fetch()
g = Git('./')
g.checkout(name)
cbpi.notify("Checkout successful", "Please restart the system")
return ('', 204)
@login_required
@route('/git/status', methods=['GET'])
def git_status(self):
repo = Repo('./')
o = repo.remotes.origin
o.fetch()
branch = repo.active_branch
url = 'https://api.github.com/repos/manuel83/craftbeerpi3/releases'
response = requests.get(url)
result = {"current_branch": branch.name, "branches": [], "releases": []}
result["branches"].append({"name": "master"})
for branch in repo.branches:
result["branches"].append({"name": branch.name})
for r in response.json():
result["releases"].append({"name": "tags/%s" % r.get("tag_name")})
"""
Check for GIT status
---
tags:
- system
responses:
200:
description: Git Status
"""
return json.dumps(result)
@login_required
@route('/check_update', methods=['GET'])
def check_update(self):
"""
Check for GIT update
---
tags:
- system
responses:
200:
description: Git Changes
"""
repo = Repo('./')
o = repo.remotes.origin
o.fetch()
changes = []
commits_behind = repo.iter_commits('master..origin/master')
for c in list(commits_behind):
changes.append({"committer": c.committer.name, "message": c.message})
return json.dumps(changes)
@login_required
@route('/git/pull', methods=['POST'])
def update(self):
"""
System Update
---
tags:
- system
responses:
200:
description: Git Pull Triggered
"""
repo = Repo('./')
o = repo.remotes.origin
info = o.pull()
cbpi.notify("Pull successful", "The lasted updated was downloaded. Please restart the system")
return ('', 204)
@route('/connect', methods=['GET'])
def connect(self):
"""
Connect
---
tags:
- system
responses:
200:
description: CraftBeerPi System Cache
"""
if cbpi.get_config_parameter("password_security", "NO") == "NO":
user = User()
user.id = "craftbeerpi"
flask_login.login_user(user)
if self.api.get_config_parameter("setup", "YES") == "YES":
return json.dumps(dict(setup=True, loggedin= current_user.is_authenticated ))
else:
return json.dumps(dict(setup=False, loggedin= current_user.is_authenticated))
@login_required
@route('/dump', methods=['GET'])
def dump(self):
"""
Dump Cache
---
tags:
- system
responses:
200:
description: CraftBeerPi System Cache
"""
return Response(response=json.dumps(cbpi.cache, sort_keys=True, indent=4), status=200, mimetype='application/json')
@cbpi.addon.core.initializer()
def init(cbpi):
SystemView.api = cbpi
SystemView.register(cbpi._app, route_base='/api/system')
+145
View File
@@ -0,0 +1,145 @@
import yaml
from flask import json, url_for, Response
from flask_classy import FlaskView, route
from git import Repo, Git
from modules.app_config import cbpi
import pprint
import time
class SystemView(FlaskView):
def doShutdown(self):
time.sleep(5)
from subprocess import call
call("halt")
@route('/shutdown', methods=['POST'])
def shutdown(self):
"""
Shutdown hook
:return: HTTP 204
"""
self.doShutdown()
return ('', 204)
def doReboot(self):
time.sleep(5)
from subprocess import call
call("reboot")
@route('/reboot', methods=['POST'])
def reboot(self):
"""
Reboot hook
:return: HTTP 204
"""
self.doReboot()
return ('', 204)
@route('/tags/<name>', methods=['GET'])
def checkout_tag(self,name):
repo = Repo('./')
repo.git.reset('--hard')
o = repo.remotes.origin
o.fetch()
g = Git('./')
g.checkout(name)
cbpi.notify("Checkout successful", "Please restart the system")
return ('', 204)
@route('/git/status', methods=['GET'])
def git_status(self):
repo = Repo('./')
o = repo.remotes.origin
o.fetch()
# Tags
tags = []
for t in repo.tags:
tags.append({"name": t.name, "commit": str(t.commit), "date": t.commit.committed_date,
"committer": t.commit.committer.name, "message": t.commit.message})
try:
branch_name = repo.active_branch.name
# test1
except:
branch_name = None
changes = []
commits_behind = repo.iter_commits('master..origin/master')
for c in list(commits_behind):
changes.append({"committer": c.committer.name, "message": c.message})
return json.dumps({"tags": tags, "headcommit": str(repo.head.commit), "branchname": branch_name,
"master": {"changes": changes}})
@route('/check_update', methods=['GET'])
def check_update(self):
repo = Repo('./')
o = repo.remotes.origin
o.fetch()
changes = []
commits_behind = repo.iter_commits('master..origin/master')
for c in list(commits_behind):
changes.append({"committer": c.committer.name, "message": c.message})
return json.dumps(changes)
@route('/git/pull', methods=['POST'])
def update(self):
repo = Repo('./')
o = repo.remotes.origin
info = o.pull()
cbpi.notify("Pull successful", "The lasted updated was downloaded. Please restart the system")
return ('', 204)
@route('/dump', methods=['GET'])
def dump(self):
return json.dumps(cbpi.cache)
@route('/endpoints', methods=['GET'])
def endpoints(self):
import urllib
output = []
vf = self.api.app.view_functions
for f in self.api.app.view_functions:
print f
endpoints = {}
re = {
"swagger": "2.0",
"host": "",
"info": {
"description":"",
"version": "",
"title": "CraftBeerPi"
},
"schemes": ["http"],
"paths": endpoints}
for rule in self.api.app.url_map.iter_rules():
r = rule
endpoints[rule.rule] = {}
if "HEAD" in r.methods: r.methods.remove("HEAD")
if "OPTIONS" in r.methods: r.methods.remove("OPTIONS")
for m in rule.methods:
endpoints[rule.rule][m] = dict(summary="", description="", consumes=["application/json"],produces=["application/json"])
with open("config/version.yaml", 'r') as stream:
y = yaml.load(stream)
pprint.pprint(y)
pprint.pprint(re)
return Response(yaml.dump(re), mimetype='text/yaml')
@cbpi.initalizer()
def init(cbpi):
SystemView.api = cbpi
SystemView.register(cbpi.app, route_base='/api/system')
+1 -32
View File
@@ -1,32 +1 @@
from flask import Blueprint,render_template import endpoints
from modules.core.core import cbpi
react = Blueprint('ui', __name__, template_folder='templates', static_folder='static')
@cbpi.addon.core.initializer(order=10)
def init(cbpi):
cbpi._app.register_blueprint(react, url_prefix='/ui')
@react.route('/', methods=["GET"])
def index():
#return react.send_static_file("index.html")
js_files = []
for key, value in cbpi.cache["js"].iteritems():
js_files.append(value)
return render_template('index.html', js_files=js_files)
@cbpi._app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404
+25
View File
@@ -0,0 +1,25 @@
from flask import Blueprint
from modules import cbpi
react = Blueprint('react', __name__, template_folder='templates', static_folder='static')
@cbpi.initalizer(order=10)
def init(cbpi):
cbpi.app.register_blueprint(react, url_prefix='/ui')
@react.route('/', methods=["GET"])
def index():
return react.send_static_file("index.html")
+2 -1
View File
@@ -7,7 +7,8 @@
} }
.container-fluid { .container-fluid {
padding-right: 5px;
padding-left: 5px;
} }
+3 -1
View File
@@ -1,3 +1,5 @@
body { body {
margin: 0;
padding: 0;
font-family: sans-serif;
} }
File diff suppressed because one or more lines are too long
-1
View File
@@ -17,6 +17,5 @@
<div id="root" ></div> <div id="root" ></div>
<script src="static/bundle.js" type="text/javascript"></script> <script src="static/bundle.js" type="text/javascript"></script>
</body> </body>
</html> </html>
-29
View File
@@ -1,29 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<link rel="stylesheet" href="/ui/static/bootstrap.min.css">
<link rel="stylesheet" href="/ui/static/css/font-awesome.min.css">
<link rel="stylesheet" href="/ui/static/bootstrap.dark.css">
<link href="https://fonts.googleapis.com/css?family=Dosis:200,500" rel="stylesheet">
<style>
body, h1, h2, h3, h4, h5, h6 {
font-family: 'Dosis', sans-serif;
}
</style>
<title>CraftBeerPi 3.1</title>
</head>
<body>
<h1>CraftBeerPi - Page not Found!</h1>
</body>
</html>
-33
View File
@@ -1,33 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<link rel="stylesheet" href="static/bootstrap.min.css">
<link rel="stylesheet" href="static/css/font-awesome.min.css">
<link rel="stylesheet" href="static/bootstrap.dark.css">
<link href="https://fonts.googleapis.com/css?family=Dosis:200,500" rel="stylesheet">
<style>
body, h1, h2, h3, h4, h5, h6 {
font-family: 'Dosis', sans-serif;
}
</style>
<title>CraftBeerPi 3.1</title>
</head>
<body>
<div id="root" ></div>
{% for file in js_files %}
<script src="{{ file }}" type="text/javascript"></script>
{% endfor %}
<script src="static/bundle.js" type="text/javascript"></script>
</body>
</html>
+3 -5
View File
@@ -1,15 +1,13 @@
Flask==0.11.1 Flask==0.12.4
Flask-SocketIO==2.6.2 Flask-SocketIO==2.6.2
flask_login==0.4.0
flask_swagger==0.2.13
eventlet==0.19.0 eventlet==0.19.0
greenlet==0.4.10 greenlet==0.4.10
python-dateutil==2.5.3 python-dateutil==2.5.3
python-engineio==0.9.2 python-engineio==0.9.2
python-mimeparse==1.5.2 python-mimeparse==1.5.2
python-socketio==1.4.4 python-socketio==1.4.4
PyYAML==3.11 PyYAML==4.2b1
requests==2.11.0 requests==2.20.0
Werkzeug==0.11.10 Werkzeug==0.11.10
httplib2==0.9.2 httplib2==0.9.2
flask-classy==0.6.10 flask-classy==0.6.10
+6 -28
View File
@@ -1,33 +1,11 @@
#!/usr/bin/env python #!/usr/bin/env python
from modules.core.core import * from modules import socketio, app, cbpi
cbpi = CraftBeerPI() try:
port = int(cbpi.get_config_parameter('port', '5000'))
except ValueError:
port = 5000
addon = cbpi.addon socketio.run(app, host='0.0.0.0', port=port)
from modules.core.db_migrate import *
from modules.buzzer import *
from modules.config import *
from modules.login import *
from modules.system import *
from modules.ui import *
from modules.step import *
from modules.kettle import *
from modules.actor import *
from modules.plugin import *
from modules.logs import *
from modules.notification import *
from modules.sensor import *
from modules.recipe_import import *
from modules.fermenter import *
from modules.action import *
from modules.base_plugins.actor import *
from modules.base_plugins.sensor import *
from modules.base_plugins.steps import *
from modules.example_plugins.WebViewJquery import *
from modules.example_plugins.WebViewReactJs import *
from modules.example_plugins.swagger import *
cbpi.run()
-2
View File
@@ -1,2 +0,0 @@
INSERT OR IGNORE INTO config VALUES ('password', 'beer', 'text', 'LoginPassword', NULL );
INSERT OR IGNORE INTO config VALUES ('password_security', 'NO', 'select', 'Show Login Dialog', '["YES","NO"]');