mirror of
https://github.com/Manuel83/craftbeerpi3
synced 2026-08-09 18:52:40 +02:00
Compare commits
51
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37fa42cfbc | ||
|
|
2b095c8c77 | ||
|
|
63f9f5468b | ||
|
|
16ad8daf44 | ||
|
|
67a103d1f3 | ||
|
|
274f282e11 | ||
|
|
4e432e05bd | ||
|
|
eda209422f | ||
|
|
5b47b94833 | ||
|
|
98409d9895 | ||
|
|
433b1c30f1 | ||
|
|
a477cb83a8 | ||
|
|
a22dcba77c | ||
|
|
c1d94a8f61 | ||
|
|
dd64f4ee16 | ||
|
|
1414b1ed3b | ||
|
|
acd404622b | ||
|
|
d657077570 | ||
|
|
0e6da89cfd | ||
|
|
faa2b9d32c | ||
|
|
83699f1933 | ||
|
|
fea3e85053 | ||
|
|
5efa1df350 | ||
|
|
dd5b5f456a | ||
|
|
e0e3aff929 | ||
|
|
2a02979e37 | ||
|
|
a1740074ac | ||
|
|
1b3042a082 | ||
|
|
b279dcf347 | ||
|
|
211edc1bbc | ||
|
|
b56c10a8a5 | ||
|
|
8d2c5c916a | ||
|
|
d637c08eba | ||
|
|
0447621895 | ||
|
|
03395647ed | ||
|
|
65833b1249 | ||
|
|
25ace8024c | ||
|
|
820061cf09 | ||
|
|
c1c5faa1f3 | ||
|
|
837b906af1 | ||
|
|
0f42153591 | ||
|
|
5fc5684e56 | ||
|
|
dc908268dd | ||
|
|
14e4e6d61a | ||
|
|
ae7e3a2e7b | ||
|
|
3eee952a04 | ||
|
|
88b360a8d7 | ||
|
|
3761713219 | ||
|
|
a96c678dd9 | ||
|
|
e7765a2c4c | ||
|
|
74b56937e7 |
+14
@@ -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"]
|
||||
@@ -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.
|
||||
|
||||
## 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
|
||||
|
||||
CraftBeerPi is a free & open source project. If you like to support the project I happy about a donation:
|
||||
|
||||
+2
-1
@@ -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 ('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 ('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 ('brew_name', '', 'text', 'Brew Name', NULL);
|
||||
INSERT OR IGNORE INTO config VALUES ('donation_notification', 'YES', 'select', 'Disable Donation Notification', '["YES","NO"]');
|
||||
|
||||
@@ -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
@@ -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
@@ -19,7 +19,8 @@ show_menu () {
|
||||
"7" "Software Update (git pull)" \
|
||||
"8" "Reset File Changes (git reset --hard)" \
|
||||
"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=$?
|
||||
# Exit if user pressed cancel or escape
|
||||
@@ -34,14 +35,6 @@ show_menu () {
|
||||
apt-get -y update; apt-get -y upgrade;
|
||||
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
|
||||
easy_install pip
|
||||
apt-get -y install python-dev
|
||||
@@ -143,6 +136,17 @@ show_menu () {
|
||||
show_menu
|
||||
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
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -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("##########################################")
|
||||
@@ -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
+22
-139
@@ -1,12 +1,12 @@
|
||||
import time
|
||||
from flask_classy import route
|
||||
from flask_login import login_required
|
||||
|
||||
from modules.core.db import DBModel
|
||||
from modules.core.core import cbpi
|
||||
from modules import DBModel, cbpi
|
||||
from modules.core.baseview import BaseView
|
||||
from modules.database.dbmodel import Actor
|
||||
|
||||
class Actor(DBModel):
|
||||
__fields__ = ["name","type", "config", "hide"]
|
||||
__table_name__ = "actor"
|
||||
__json_fields__ = ["config"]
|
||||
|
||||
class ActorView(BaseView):
|
||||
model = Actor
|
||||
@@ -14,178 +14,61 @@ class ActorView(BaseView):
|
||||
|
||||
@classmethod
|
||||
def post_init_callback(self, obj):
|
||||
|
||||
obj.state = 0
|
||||
obj.power = 100
|
||||
|
||||
def _post_post_callback(self, m):
|
||||
self.api.actor.init_one(m.id)
|
||||
self.api.init_actor(m.id)
|
||||
|
||||
def _post_put_callback(self, m):
|
||||
self.api.actor.init_one(m.id)
|
||||
|
||||
@login_required
|
||||
self.api.init_actor(m.id)
|
||||
|
||||
@route("<int:id>/switch/on", methods=["POST"])
|
||||
def on(self, id):
|
||||
"""
|
||||
Switch actor on
|
||||
---
|
||||
tags:
|
||||
- actor
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
schema:
|
||||
type: integer
|
||||
required: true
|
||||
description: Numeric ID of the actor
|
||||
responses:
|
||||
200:
|
||||
description: Actor switched on
|
||||
"""
|
||||
self.api.actor.on(id)
|
||||
self.api.switch_actor_on(id)
|
||||
return ('', 204)
|
||||
|
||||
@login_required
|
||||
@route("<int:id>/switch/off", methods=["POST"])
|
||||
def off(self, id):
|
||||
"""
|
||||
Switch actor off
|
||||
---
|
||||
tags:
|
||||
- actor
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
schema:
|
||||
type: integer
|
||||
required: true
|
||||
description: Numeric ID of the actor
|
||||
responses:
|
||||
200:
|
||||
description: Actor switched off
|
||||
"""
|
||||
self.api.actor.off(id)
|
||||
self.api.switch_actor_off(id)
|
||||
return ('', 204)
|
||||
|
||||
@login_required
|
||||
@route("<int:id>/power/<int:power>", methods=["POST"])
|
||||
def power(self, id, power):
|
||||
"""
|
||||
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)
|
||||
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)
|
||||
|
||||
if self.api.cache.get("actors").get(id).state == 0:
|
||||
self.on(id)
|
||||
else:
|
||||
self.off(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.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)
|
||||
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)
|
||||
cbpi.cache.get("actors").get(id).instance.__getattribute__(method)()
|
||||
return ('', 204)
|
||||
|
||||
|
||||
@cbpi.addon.core.initializer(order=1000)
|
||||
@cbpi.initalizer(order=1000)
|
||||
def init(cbpi):
|
||||
ActorView.register(cbpi._app, route_base='/api/actor')
|
||||
ActorView.register(cbpi.app, route_base='/api/actor')
|
||||
ActorView.init_cache()
|
||||
#cbpi.init_actors()
|
||||
cbpi.init_actors()
|
||||
@@ -0,0 +1 @@
|
||||
import endpoints
|
||||
@@ -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')
|
||||
@@ -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("##########################################")
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
@@ -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")
|
||||
@@ -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
+20
-16
@@ -1,44 +1,45 @@
|
||||
import time
|
||||
from thread import start_new_thread
|
||||
|
||||
from modules.core.baseapi import Buzzer
|
||||
from modules.core.core import cbpi
|
||||
from modules import cbpi
|
||||
|
||||
try:
|
||||
import RPi.GPIO as GPIO
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
class GPIOBuzzer(Buzzer):
|
||||
class Buzzer(object):
|
||||
|
||||
sound = ["H", 0.1, "L", 0.1, "H", 0.1, "L", 0.1, "H", 0.1, "L"]
|
||||
|
||||
|
||||
def __init__(self, gpio):
|
||||
def __init__(self, gpio, beep_level):
|
||||
try:
|
||||
cbpi._app.logger.info("INIT BUZZER NOW GPIO%s" % gpio)
|
||||
cbpi.app.logger.info("INIT BUZZER NOW GPIO%s" % gpio)
|
||||
self.gpio = int(gpio)
|
||||
self.beep_level = beep_level
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
GPIO.setup(self.gpio, GPIO.OUT)
|
||||
self.state = True
|
||||
cbpi._app.logger.info("BUZZER SETUP OK")
|
||||
cbpi.app.logger.info("BUZZER SETUP OK")
|
||||
except Exception as e:
|
||||
cbpi._app.logger.info("BUZZER EXCEPTION %s" % str(e))
|
||||
cbpi.app.logger.info("BUZZER EXCEPTION %s" % str(e))
|
||||
self.state = False
|
||||
|
||||
def beep(self):
|
||||
if self.state is False:
|
||||
cbpi._app.logger.error("BUZZER not working")
|
||||
cbpi.app.logger.error("BUZZER not working")
|
||||
return
|
||||
|
||||
def play(sound):
|
||||
try:
|
||||
for i in sound:
|
||||
if (isinstance(i, str)):
|
||||
if i == "H":
|
||||
if i == "H" and self.beep_level == "HIGH":
|
||||
GPIO.output(int(self.gpio), GPIO.HIGH)
|
||||
else:
|
||||
elif i == "H" and self.beep_level != "HIGH":
|
||||
GPIO.output(int(self.gpio), GPIO.LOW)
|
||||
elif i == "L" and self.beep_level == "HIGH":
|
||||
GPIO.output(int(self.gpio), GPIO.LOW)
|
||||
else:
|
||||
GPIO.output(int(self.gpio), GPIO.HIGH)
|
||||
else:
|
||||
time.sleep(i)
|
||||
except Exception as e:
|
||||
@@ -46,8 +47,11 @@ class GPIOBuzzer(Buzzer):
|
||||
|
||||
start_new_thread(play, (self.sound,))
|
||||
|
||||
@cbpi.addon.core.initializer(order=1)
|
||||
@cbpi.initalizer(order=1)
|
||||
def init(cbpi):
|
||||
gpio = cbpi.get_config_parameter("buzzer", 16)
|
||||
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
+12
-44
@@ -2,10 +2,14 @@ import time
|
||||
|
||||
from flask import json, request
|
||||
from flask_classy import route
|
||||
from modules.core.core import cbpi
|
||||
from modules.core.db import DBModel
|
||||
from modules import DBModel, cbpi, get_db
|
||||
from modules.core.baseview import BaseView
|
||||
from modules.database.dbmodel import Config
|
||||
|
||||
class Config(DBModel):
|
||||
__fields__ = ["type", "value", "description", "options"]
|
||||
__table_name__ = "config"
|
||||
__json_fields__ = ["options"]
|
||||
__priamry_key__ = "name"
|
||||
|
||||
|
||||
class ConfigView(BaseView):
|
||||
@@ -14,15 +18,7 @@ class ConfigView(BaseView):
|
||||
|
||||
@route('/<name>', methods=["PUT"])
|
||||
def put(self, name):
|
||||
"""
|
||||
Set new config value
|
||||
---
|
||||
tags:
|
||||
- config
|
||||
responses:
|
||||
204:
|
||||
description: New config value set
|
||||
"""
|
||||
|
||||
data = request.json
|
||||
data["name"] = name
|
||||
update_data = {"name": data["name"], "value": data["value"]}
|
||||
@@ -31,59 +27,31 @@ class ConfigView(BaseView):
|
||||
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__)
|
||||
self._post_put_callback(self.api.cache.get(self.cache_key)[name])
|
||||
|
||||
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=["GET"])
|
||||
def getOne(self, id):
|
||||
"""
|
||||
Get config parameter
|
||||
---
|
||||
tags:
|
||||
- config
|
||||
responses:
|
||||
400:
|
||||
description: Get one config parameter via web api is not supported
|
||||
"""
|
||||
return ('NOT SUPPORTED', 400)
|
||||
|
||||
@route('/<id>', methods=["DELETE"])
|
||||
def delete(self, id):
|
||||
"""
|
||||
Delete config parameter
|
||||
---
|
||||
tags:
|
||||
- config
|
||||
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():
|
||||
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)
|
||||
@cbpi.initalizer(order=0)
|
||||
def init(cbpi):
|
||||
ConfigView.register(cbpi._app, route_base='/api/config')
|
||||
|
||||
ConfigView.register(cbpi.app, route_base='/api/config')
|
||||
ConfigView.init_cache()
|
||||
|
||||
Executable → Regular
@@ -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
|
||||
@@ -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
+4
-11
@@ -1,8 +1,6 @@
|
||||
from flask import request, json
|
||||
from flask_classy import route, FlaskView
|
||||
from flask_login import login_required
|
||||
|
||||
from modules.core.core import cbpi
|
||||
from modules import cbpi
|
||||
|
||||
|
||||
class BaseView(FlaskView):
|
||||
@@ -11,15 +9,14 @@ class BaseView(FlaskView):
|
||||
cache_key = None
|
||||
api = cbpi
|
||||
|
||||
@login_required
|
||||
@route('/<int:id>', methods=["GET"])
|
||||
def getOne(self, id):
|
||||
|
||||
if self.api.cache.get(self.cache_key) is not None:
|
||||
return json.dumps(self.api.cache.get(self.cache_key).get(id))
|
||||
else:
|
||||
return json.dumps(self.model.get_one(id))
|
||||
|
||||
@login_required
|
||||
@route('/', methods=["GET"])
|
||||
def getAll(self):
|
||||
if self.api.cache.get(self.cache_key) is not None:
|
||||
@@ -34,12 +31,9 @@ class BaseView(FlaskView):
|
||||
def _post_post_callback(self, m):
|
||||
pass
|
||||
|
||||
@login_required
|
||||
@route('/', methods=["POST"])
|
||||
def post(self):
|
||||
|
||||
data = request.json
|
||||
self.api._app.logger.info("INSERT Model %s", self.model.__name__)
|
||||
self._pre_post_callback(data)
|
||||
m = self.model.insert(**data)
|
||||
if self.api.cache.get(self.cache_key) is not None:
|
||||
@@ -55,7 +49,7 @@ class BaseView(FlaskView):
|
||||
def _post_put_callback(self, m):
|
||||
pass
|
||||
|
||||
@login_required
|
||||
|
||||
@route('/<int:id>', methods=["PUT"])
|
||||
def put(self, id):
|
||||
data = request.json
|
||||
@@ -83,7 +77,6 @@ class BaseView(FlaskView):
|
||||
def _post_delete_callback(self, id):
|
||||
pass
|
||||
|
||||
@login_required
|
||||
@route('/<int:id>', methods=["DELETE"])
|
||||
def delete(self, id):
|
||||
if self.api.cache.get(self.cache_key) is not None:
|
||||
@@ -101,7 +94,7 @@ class BaseView(FlaskView):
|
||||
|
||||
@classmethod
|
||||
def init_cache(cls):
|
||||
with cls.api._app.app_context():
|
||||
with cls.api.app.app_context():
|
||||
|
||||
if cls.model.__as_array__ is True:
|
||||
cls.api.cache[cls.cache_key] = []
|
||||
|
||||
@@ -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
+430
-333
@@ -1,204 +1,153 @@
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import inspect
|
||||
import pprint
|
||||
|
||||
import sqlite3
|
||||
import uuid
|
||||
from flask import make_response, g
|
||||
import datetime
|
||||
from datetime import datetime
|
||||
from functools import wraps, update_wrapper
|
||||
from importlib import import_module
|
||||
from flask.views import MethodView
|
||||
from flask_classy import FlaskView, route
|
||||
|
||||
from time import localtime, strftime
|
||||
from functools import wraps, update_wrapper
|
||||
|
||||
|
||||
from props import *
|
||||
|
||||
from hardware import *
|
||||
|
||||
import time
|
||||
|
||||
from flask import Flask, redirect, json, g, make_response
|
||||
from flask_socketio import SocketIO
|
||||
|
||||
from baseapi import *
|
||||
from db import DBModel
|
||||
from modules.core.basetypes import Sensor, Actor
|
||||
from modules.database.dbmodel import Kettle
|
||||
import uuid
|
||||
|
||||
|
||||
|
||||
class ComplexEncoder(json.JSONEncoder):
|
||||
def default(self, obj):
|
||||
try:
|
||||
|
||||
if isinstance(obj, DBModel):
|
||||
return obj.__dict__
|
||||
elif isinstance(obj, Actor):
|
||||
return {"state": obj.value}
|
||||
elif isinstance(obj, Sensor):
|
||||
return {"value": obj.value, "unit": obj.unit}
|
||||
elif hasattr(obj, "callback"):
|
||||
return obj()
|
||||
else:
|
||||
return None
|
||||
|
||||
return None
|
||||
except TypeError as e:
|
||||
class NotificationAPI(object):
|
||||
pass
|
||||
return None
|
||||
|
||||
class ActorAPI(object):
|
||||
|
||||
class Addon(object):
|
||||
def __init__(self, cbpi):
|
||||
self.step = StepAPI(cbpi)
|
||||
self.actor = ActorAPI(cbpi)
|
||||
self.sensor = SensorAPI(cbpi)
|
||||
self.kettle = KettleAPI(cbpi)
|
||||
self.fermenter = FermenterAPI(cbpi)
|
||||
self.core = CoreAPI(cbpi)
|
||||
def init_actors(self):
|
||||
self.app.logger.info("Init Actors")
|
||||
t = self.cache.get("actor_types")
|
||||
for key, value in t.iteritems():
|
||||
value.get("class").api = self
|
||||
value.get("class").init_global()
|
||||
|
||||
def init(self):
|
||||
self.core.init()
|
||||
self.step.init()
|
||||
self.actor.init()
|
||||
self.sensor.init()
|
||||
# self.kettle.init()
|
||||
# self.fermenter.init()
|
||||
for key in self.cache.get("actors"):
|
||||
self.init_actor(key)
|
||||
|
||||
|
||||
class ActorCore(object):
|
||||
key = "actor_types"
|
||||
|
||||
def __init__(self, cbpi):
|
||||
self.cbpi = cbpi
|
||||
self.cbpi.cache["actors"] = {}
|
||||
self.cbpi.cache[self.key] = {}
|
||||
|
||||
def init(self):
|
||||
for key, value in self.cbpi.cache["actors"].iteritems():
|
||||
self.init_one(key)
|
||||
|
||||
def init_one(self, id):
|
||||
def init_actor(self, id):
|
||||
try:
|
||||
|
||||
actor = self.cbpi.cache["actors"][id]
|
||||
clazz = self.cbpi.cache[self.key].get(actor.type)["class"]
|
||||
cfg = actor.config.copy()
|
||||
cfg.update(dict(cbpi=self.cbpi, id=id))
|
||||
self.cbpi.cache["actors"][id].instance = clazz(**cfg)
|
||||
actor.state = 0
|
||||
actor.power = 100
|
||||
self.cbpi.emit("INIT_ACTOR", id=id)
|
||||
value = self.cache.get("actors").get(int(id))
|
||||
cfg = value.config.copy()
|
||||
cfg.update(dict(api=self, id=id, name=value.name))
|
||||
clazz = self.cache.get("actor_types").get(value.type).get("class")
|
||||
value.instance = clazz(**cfg)
|
||||
value.instance.init()
|
||||
value.state = 0
|
||||
value.power = 100
|
||||
except Exception as e:
|
||||
print e
|
||||
self.cbpi._app.logger.error(e)
|
||||
self.notify("Actor Error", "Failed to setup actor %s. Please check the configuraiton" % value.name,
|
||||
type="danger", timeout=None)
|
||||
self.app.logger.error("Initializing of Actor %s failed" % id)
|
||||
|
||||
def stop_one(self, id):
|
||||
self.cbpi.cache["actors"][id]["instance"].stop()
|
||||
self.cbpi.emit("STOP_ACTOR", id=id)
|
||||
def switch_actor_on(self, id, power=None):
|
||||
actor = self.cache.get("actors").get(id)
|
||||
|
||||
def on(self, id, power=100):
|
||||
try:
|
||||
actor = self.cbpi.cache["actors"].get(int(id))
|
||||
actor.instance.on()
|
||||
if actor.state == 1:
|
||||
return
|
||||
|
||||
actor.instance.on(power=power)
|
||||
actor.state = 1
|
||||
actor.power = power
|
||||
self.cbpi.ws_emit("SWITCH_ACTOR", actor)
|
||||
self.cbpi.emit("SWITCH_ACTOR_ON", id=id, power=power)
|
||||
return True
|
||||
except Exception as e:
|
||||
print e
|
||||
return False
|
||||
if power is not None:
|
||||
|
||||
def off(self, id):
|
||||
try:
|
||||
actor = self.cbpi.cache["actors"].get(int(id))
|
||||
actor.power = power
|
||||
self.emit("SWITCH_ACTOR", actor)
|
||||
|
||||
def actor_power(self, id, power=100):
|
||||
actor = self.cache.get("actors").get(id)
|
||||
actor.instance.set_power(power=power)
|
||||
actor.power = power
|
||||
self.emit("SWITCH_ACTOR", actor)
|
||||
|
||||
def switch_actor_off(self, id):
|
||||
actor = self.cache.get("actors").get(id)
|
||||
|
||||
if actor.state == 0:
|
||||
return
|
||||
actor.instance.off()
|
||||
actor.state = 0
|
||||
self.cbpi.ws_emit("SWITCH_ACTOR", actor)
|
||||
self.cbpi.emit("SWITCH_ACTOR_OFF", id=id)
|
||||
return True
|
||||
except Exception as e:
|
||||
print e
|
||||
return False
|
||||
self.emit("SWITCH_ACTOR", actor)
|
||||
|
||||
def toggle(self, id):
|
||||
if self.cbpi.cache.get("actors").get(id).state == 0:
|
||||
self.on(id)
|
||||
class SensorAPI(object):
|
||||
|
||||
def init_sensors(self):
|
||||
'''
|
||||
Initialize all sensors
|
||||
:return:
|
||||
'''
|
||||
|
||||
self.app.logger.info("Init Sensors")
|
||||
|
||||
t = self.cache.get("sensor_types")
|
||||
for key, value in t.iteritems():
|
||||
value.get("class").init_global()
|
||||
|
||||
for key in self.cache.get("sensors"):
|
||||
self.init_sensor(key)
|
||||
|
||||
def stop_sensor(self, id):
|
||||
|
||||
try:
|
||||
self.cache.get("sensors").get(id).instance.stop()
|
||||
except Exception as e:
|
||||
|
||||
self.app.logger.info("Stop Sensor Error")
|
||||
pass
|
||||
|
||||
|
||||
def init_sensor(self, id):
|
||||
'''
|
||||
initialize sensor by id
|
||||
:param id:
|
||||
:return:
|
||||
'''
|
||||
|
||||
def start_active_sensor(instance):
|
||||
'''
|
||||
start active sensors as background job
|
||||
:param instance:
|
||||
:return:
|
||||
'''
|
||||
instance.execute()
|
||||
|
||||
try:
|
||||
if id in self.cache.get("sensor_instances"):
|
||||
self.cache.get("sensor_instances").get(id).stop()
|
||||
value = self.cache.get("sensors").get(id)
|
||||
|
||||
cfg = value.config.copy()
|
||||
cfg.update(dict(api=self, id=id, name=value.name))
|
||||
clazz = self.cache.get("sensor_types").get(value.type).get("class")
|
||||
value.instance = clazz(**cfg)
|
||||
value.instance.init()
|
||||
if isinstance(value.instance, SensorPassive):
|
||||
# Passive Sensors
|
||||
value.mode = "P"
|
||||
else:
|
||||
self.off(id)
|
||||
|
||||
def power(self, id, power):
|
||||
try:
|
||||
actor = self.cbpi.cache["actors"].get(int(id))
|
||||
actor.instance.power(power)
|
||||
actor.power = power
|
||||
self.cbpi.ws_emit("SWITCH_ACTOR", actor)
|
||||
self.cbpi.emit("SWITCH_ACTOR_POWER_CHANGE", id=id, power=power)
|
||||
return True
|
||||
except Exception as e:
|
||||
print e
|
||||
return False
|
||||
|
||||
def action(self, id, method):
|
||||
self.cbpi.cache.get("actors").get(id).instance.__getattribute__(method)()
|
||||
|
||||
|
||||
def toggle_timeout(self, id, seconds):
|
||||
|
||||
def toggle( id, seconds):
|
||||
self.cbpi.cache.get("actors").get(int(id)).timer = int(time.time()) + int(seconds)
|
||||
self.toggle(int(id))
|
||||
self.cbpi.sleep(seconds)
|
||||
self.cbpi.cache.get("actors").get(int(id)).timer = None
|
||||
self.toggle(int(id))
|
||||
job = self.cbpi._socketio.start_background_task(target=toggle, id=id, seconds=seconds)
|
||||
|
||||
def get_state(self, actor_id):
|
||||
pass
|
||||
|
||||
|
||||
class SensorCore(object):
|
||||
key = "sensor_types"
|
||||
|
||||
def __init__(self, cbpi):
|
||||
self.cbpi = cbpi
|
||||
self.cbpi.cache["sensors"] = {}
|
||||
self.cbpi.cache["sensor_instances"] = {}
|
||||
self.cbpi.cache["sensor_types"] = {}
|
||||
|
||||
def init(self):
|
||||
for key, value in self.cbpi.cache["sensors"].iteritems():
|
||||
self.init_one(key)
|
||||
|
||||
def init_one(self, id):
|
||||
try:
|
||||
sensor = self.cbpi.cache["sensors"][id]
|
||||
clazz = self.cbpi.cache[self.key].get(sensor.type)["class"]
|
||||
cfg = sensor.config.copy()
|
||||
cfg.update(dict(cbpi=self.cbpi, id=id))
|
||||
self.cbpi.cache["sensors"][id].instance = clazz(**cfg)
|
||||
self.cbpi.cache["sensors"][id].instance.init()
|
||||
|
||||
self.cbpi.emit("INIT_SENSOR", id=id)
|
||||
|
||||
def job(obj):
|
||||
obj.execute()
|
||||
|
||||
t = self.cbpi._socketio.start_background_task(target=job, obj=self.cbpi.cache["sensors"][id].instance)
|
||||
self.cbpi.emit("INIT_SENSOR", id=id)
|
||||
# Active Sensors
|
||||
value.mode = "A"
|
||||
t = self.socketio.start_background_task(target=start_active_sensor, instance=value.instance)
|
||||
|
||||
except Exception as e:
|
||||
print "ERROR"
|
||||
self.cbpi._app.logger.error(e)
|
||||
|
||||
def stop_one(self, id):
|
||||
self.notify("Sensor Error", "Failed to setup Sensor %s. Please check the configuraiton" % value.name, type="danger", timeout=None)
|
||||
self.app.logger.error("Initializing of Sensor %s failed" % id)
|
||||
|
||||
self.cbpi.cache["sensors"][id].instance.stop()
|
||||
self.cbpi.emit("STOP_SENSOR", id=id)
|
||||
def receive_sensor_value(self, id, value):
|
||||
self.emit("SENSOR_UPDATE", self.cache.get("sensors")[id])
|
||||
self.save_to_file(id, value)
|
||||
|
||||
def get_value(self, sensorid):
|
||||
try:
|
||||
return self.cbpi.cache["sensors"][sensorid].instance.value
|
||||
except:
|
||||
return None
|
||||
|
||||
def get_state(self, actor_id):
|
||||
pass
|
||||
|
||||
def write_log(self, id, value, prefix="sensor"):
|
||||
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())
|
||||
msg = str(formatted_time) + "," +str(value) + "\n"
|
||||
@@ -206,17 +155,6 @@ class SensorCore(object):
|
||||
with open(filename, "a") as file:
|
||||
file.write(msg)
|
||||
|
||||
def action(self, id, method):
|
||||
self.cbpi.cache.get("sensors").get(id).instance.__getattribute__(method)()
|
||||
|
||||
|
||||
|
||||
class BrewingCore(object):
|
||||
def __init__(self, cbpi):
|
||||
self.cbpi = cbpi
|
||||
self.cbpi.cache["step_types"] = {}
|
||||
self.cbpi.cache["controller_types"] = {}
|
||||
|
||||
def log_action(self, text):
|
||||
filename = "./logs/action.log"
|
||||
formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime())
|
||||
@@ -224,115 +162,265 @@ class BrewingCore(object):
|
||||
text = text.encode("utf-8")
|
||||
file.write("%s,%s\n" % (formatted_time, text))
|
||||
|
||||
def get_controller(self, name):
|
||||
return self.cbpi.cache["controller_types"].get(name)
|
||||
|
||||
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__)
|
||||
self.cbpi.ws_emit("UPDATE_KETTLE_TARGET_TEMP", {"id": id, "target_temp": temp})
|
||||
self.cbpi.emit("SET_KETTLE_TARGET_TEMP", id=id, temp=temp)
|
||||
def shutdown_sensor(self, id):
|
||||
self.cache.get("sensors")[id].stop()
|
||||
|
||||
|
||||
def toggle_automatic(self, id):
|
||||
kettle = self.cbpi.cache.get("kettle")[id]
|
||||
if kettle.state is False:
|
||||
# Start controller
|
||||
if kettle.logic is not None:
|
||||
cfg = kettle.config.copy()
|
||||
cfg.update(dict(api=cbpi, kettle_id=kettle.id, heater=kettle.heater, sensor=kettle.sensor))
|
||||
instance = self.get_controller(kettle.logic).get("class")(**cfg)
|
||||
instance.init()
|
||||
kettle.instance = instance
|
||||
|
||||
def run(instance):
|
||||
instance.run()
|
||||
|
||||
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))
|
||||
self.cbpi.emit("KETTLE_CONTROLLER_STARTED", id=id)
|
||||
else:
|
||||
# Stop controller
|
||||
kettle.instance.stop()
|
||||
kettle.state = not kettle.state
|
||||
self.cbpi.ws_emit("UPDATE_KETTLE", cbpi.cache.get("kettle").get(id))
|
||||
self.cbpi.emit("KETTLE_CONTROLLER_STOPPED", id=id)
|
||||
|
||||
|
||||
class FermentationCore(object):
|
||||
def __init__(self, cbpi):
|
||||
self.cbpi = cbpi
|
||||
self.cbpi.cache["fermenter"] = {}
|
||||
self.cbpi.cache["fermentation_controller_types"] = {}
|
||||
|
||||
def get_controller(self, name):
|
||||
return self.cbpi.cache["fermentation_controller_types"].get(name)
|
||||
|
||||
|
||||
class CraftBeerPI(object):
|
||||
cache = {}
|
||||
eventbus = {}
|
||||
|
||||
def __init__(self):
|
||||
FORMAT = '%(asctime)-15s - %(levelname)s - %(message)s'
|
||||
logging.basicConfig(filename='./logs/app.log', level=logging.INFO, format=FORMAT)
|
||||
self.cache["messages"] = []
|
||||
self.cache["version"] = "3.1"
|
||||
self.modules = {}
|
||||
|
||||
self.addon = Addon(self)
|
||||
self.actor = ActorCore(self)
|
||||
self.sensor = SensorCore(self)
|
||||
self.brewing = BrewingCore(self)
|
||||
self.fermentation = FermentationCore(self)
|
||||
self._app = Flask(__name__)
|
||||
self._app.secret_key = 'Cr4ftB33rP1'
|
||||
self._app.json_encoder = ComplexEncoder
|
||||
self._socketio = SocketIO(self._app, json=json, logging=False)
|
||||
|
||||
@self._app.route('/')
|
||||
def index():
|
||||
return redirect('ui')
|
||||
|
||||
def run(self):
|
||||
self.__init_db()
|
||||
self.loadPlugins()
|
||||
self.addon.init()
|
||||
self.sensor.init()
|
||||
self.actor.init()
|
||||
self.beep()
|
||||
def get_sensor_value(self, id):
|
||||
try:
|
||||
port = int(cbpi.get_config_parameter('port', '5000'))
|
||||
except ValueError:
|
||||
port = 5000
|
||||
print port
|
||||
self._socketio.run(self._app, host='0.0.0.0', port=port)
|
||||
|
||||
def beep(self):
|
||||
self.buzzer.beep()
|
||||
|
||||
def sleep(self, seconds):
|
||||
self._socketio.sleep(seconds)
|
||||
|
||||
def notify(self, headline, message, type="success", timeout=5000):
|
||||
msg = {"id": str(uuid.uuid1()), "type": type, "headline": headline, "message": message, "timeout": timeout}
|
||||
self.ws_emit("NOTIFY", msg)
|
||||
|
||||
def ws_emit(self, key, data):
|
||||
self._socketio.emit(key, data, namespace='/brew')
|
||||
|
||||
def __init_db(self, ):
|
||||
|
||||
with self._app.app_context():
|
||||
db = self.get_db()
|
||||
try:
|
||||
with self._app.open_resource('../../config/schema.sql', mode='r') as f:
|
||||
db.cursor().executescript(f.read())
|
||||
db.commit()
|
||||
id = int(id)
|
||||
return float(self.cache.get("sensors")[id].instance.last_value)
|
||||
except Exception as e:
|
||||
|
||||
pass
|
||||
return None
|
||||
|
||||
class CacheAPI(object):
|
||||
|
||||
def get_sensor(self, id):
|
||||
try:
|
||||
return self.cache["sensors"][id]
|
||||
except:
|
||||
return None
|
||||
|
||||
def get_actor(self, id):
|
||||
try:
|
||||
return self.cache["actors"][id]
|
||||
except:
|
||||
return None
|
||||
|
||||
class CraftBeerPi(ActorAPI, SensorAPI):
|
||||
|
||||
cache = {
|
||||
"init": {},
|
||||
"config": {},
|
||||
"actor_types": {},
|
||||
"sensor_types": {},
|
||||
"sensors": {},
|
||||
"sensor_instances": {},
|
||||
"init": [],
|
||||
"background":[],
|
||||
"step_types": {},
|
||||
"controller_types": {},
|
||||
"messages": [],
|
||||
"plugins": {},
|
||||
"fermentation_controller_types": {},
|
||||
"fermenter_task": {}
|
||||
}
|
||||
buzzer = None
|
||||
eventbus = {}
|
||||
|
||||
|
||||
# constructor
|
||||
def __init__(self, app, socketio):
|
||||
self.app = app
|
||||
self.socketio = socketio
|
||||
|
||||
|
||||
def emit(self, key, data):
|
||||
self.socketio.emit(key, data, namespace='/brew')
|
||||
|
||||
def notify(self, headline, message, type="success", timeout=5000):
|
||||
self.beep()
|
||||
msg = {"id": str(uuid.uuid1()), "type": type, "headline": headline, "message": message, "timeout": timeout}
|
||||
self.emit_message(msg)
|
||||
|
||||
def beep(self):
|
||||
if self.buzzer is not None:
|
||||
self.buzzer.beep()
|
||||
|
||||
|
||||
def add_cache_callback(self, key, method):
|
||||
method.callback = True
|
||||
self.cache[key] = method
|
||||
|
||||
def get_config_parameter(self, key, default):
|
||||
cfg = self.cache.get("config").get(key)
|
||||
|
||||
if cfg is None:
|
||||
return default
|
||||
else:
|
||||
return cfg.value
|
||||
|
||||
def set_config_parameter(self, name, value):
|
||||
from modules.config import Config
|
||||
with self.app.app_context():
|
||||
update_data = {"name": name, "value": value}
|
||||
self.cache.get("config")[name].__dict__.update(**update_data)
|
||||
c = Config.update(**update_data)
|
||||
self.emit("UPDATE_CONFIG", c)
|
||||
|
||||
|
||||
def add_config_parameter(self, name, value, type, description, options=None):
|
||||
from modules.config import Config
|
||||
with self.app.app_context():
|
||||
c = Config.insert(**{"name":name, "value": value, "type": type, "description": description, "options": options})
|
||||
if self.cache.get("config") is not None:
|
||||
self.cache.get("config")[c.name] = c
|
||||
|
||||
def clear_cache(self, key, is_array=False):
|
||||
if is_array:
|
||||
self.cache[key] = []
|
||||
else:
|
||||
self.cache[key] = {}
|
||||
|
||||
# helper method for parsing props
|
||||
def __parseProps(self, key, cls):
|
||||
name = cls.__name__
|
||||
self.cache[key][name] = {"name": name, "class": cls, "properties": [], "actions": []}
|
||||
tmpObj = cls()
|
||||
members = [attr for attr in dir(tmpObj) if not callable(getattr(tmpObj, attr)) and not attr.startswith("__")]
|
||||
for m in members:
|
||||
if isinstance(tmpObj.__getattribute__(m), Property.Number):
|
||||
t = tmpObj.__getattribute__(m)
|
||||
self.cache[key][name]["properties"].append(
|
||||
{"name": m, "label": t.label, "type": "number", "configurable": t.configurable, "description": t.description, "default_value": t.default_value})
|
||||
elif isinstance(tmpObj.__getattribute__(m), Property.Text):
|
||||
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})
|
||||
elif isinstance(tmpObj.__getattribute__(m), Property.Select):
|
||||
t = tmpObj.__getattribute__(m)
|
||||
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), Property.Actor):
|
||||
t = tmpObj.__getattribute__(m)
|
||||
self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "actor", "configurable": t.configurable, "description": t.description})
|
||||
elif isinstance(tmpObj.__getattribute__(m), Property.Sensor):
|
||||
t = tmpObj.__getattribute__(m)
|
||||
self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "sensor", "configurable": t.configurable, "description": t.description})
|
||||
elif isinstance(tmpObj.__getattribute__(m), Property.Kettle):
|
||||
t = tmpObj.__getattribute__(m)
|
||||
self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "kettle", "configurable": t.configurable, "description": t.description})
|
||||
|
||||
for name, method in cls.__dict__.iteritems():
|
||||
if hasattr(method, "action"):
|
||||
label = method.__getattribute__("label")
|
||||
self.cache[key][cls.__name__]["actions"].append({"method": name, "label": label})
|
||||
|
||||
|
||||
return cls
|
||||
|
||||
|
||||
def actor(self, cls):
|
||||
return self.__parseProps("actor_types", cls)
|
||||
|
||||
|
||||
|
||||
def actor2(self, description="", power=True, **options):
|
||||
|
||||
def decorator(f):
|
||||
print f()
|
||||
print f
|
||||
print options
|
||||
print description
|
||||
return f
|
||||
return decorator
|
||||
|
||||
def sensor(self, cls):
|
||||
return self.__parseProps("sensor_types", cls)
|
||||
|
||||
def controller(self, cls):
|
||||
return self.__parseProps("controller_types", cls)
|
||||
|
||||
def fermentation_controller(self, cls):
|
||||
return self.__parseProps("fermentation_controller_types", cls)
|
||||
|
||||
def get_controller(self, name):
|
||||
return self.cache["controller_types"].get(name)
|
||||
|
||||
def get_fermentation_controller(self, name):
|
||||
return self.cache["fermentation_controller_types"].get(name)
|
||||
|
||||
|
||||
# Step action
|
||||
def action(self,label):
|
||||
def real_decorator(func):
|
||||
func.action = True
|
||||
func.label = label
|
||||
return func
|
||||
return real_decorator
|
||||
|
||||
# step decorator
|
||||
def step(self, cls):
|
||||
|
||||
key = "step_types"
|
||||
name = cls.__name__
|
||||
self.cache[key][name] = {"name": name, "class": cls, "properties": [], "actions": []}
|
||||
|
||||
tmpObj = cls()
|
||||
members = [attr for attr in dir(tmpObj) if not callable(getattr(tmpObj, attr)) and not attr.startswith("__")]
|
||||
for m in members:
|
||||
if isinstance(tmpObj.__getattribute__(m), StepProperty.Number):
|
||||
t = tmpObj.__getattribute__(m)
|
||||
self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "number", "configurable": t.configurable, "default_value": t.default_value, "description": t.description})
|
||||
elif isinstance(tmpObj.__getattribute__(m), StepProperty.Text):
|
||||
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})
|
||||
elif isinstance(tmpObj.__getattribute__(m), StepProperty.Select):
|
||||
t = tmpObj.__getattribute__(m)
|
||||
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):
|
||||
t = tmpObj.__getattribute__(m)
|
||||
self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "actor", "configurable": t.configurable, "description": t.description})
|
||||
elif isinstance(tmpObj.__getattribute__(m), StepProperty.Sensor):
|
||||
t = tmpObj.__getattribute__(m)
|
||||
self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "sensor", "configurable": t.configurable, "description": t.description})
|
||||
elif isinstance(tmpObj.__getattribute__(m), StepProperty.Kettle):
|
||||
t = tmpObj.__getattribute__(m)
|
||||
self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "kettle", "configurable": t.configurable, "description": t.description})
|
||||
|
||||
for name, method in cls.__dict__.iteritems():
|
||||
if hasattr(method, "action"):
|
||||
label = method.__getattribute__("label")
|
||||
self.cache[key][cls.__name__]["actions"].append({"method": name, "label": label})
|
||||
|
||||
return cls
|
||||
|
||||
|
||||
# Event Bus
|
||||
def event(self, name, async=False):
|
||||
|
||||
def real_decorator(function):
|
||||
if self.eventbus.get(name) is None:
|
||||
self.eventbus[name] = []
|
||||
self.eventbus[name].append({"function": function, "async": async})
|
||||
def wrapper(*args, **kwargs):
|
||||
return function(*args, **kwargs)
|
||||
return wrapper
|
||||
return real_decorator
|
||||
|
||||
def emit_message(self, message):
|
||||
self.emit_event(name="MESSAGE", message=message)
|
||||
|
||||
def emit_event(self, name, **kwargs):
|
||||
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)
|
||||
@@ -347,50 +435,59 @@ class CraftBeerPI(object):
|
||||
|
||||
return update_wrapper(no_cache, view)
|
||||
|
||||
def get_db(self):
|
||||
db = getattr(g, '_database', None)
|
||||
if db is None:
|
||||
def dict_factory(cursor, row):
|
||||
d = {}
|
||||
for idx, col in enumerate(cursor.description):
|
||||
d[col[0]] = row[idx]
|
||||
return d
|
||||
|
||||
db = g._database = sqlite3.connect('craftbeerpi.db')
|
||||
db.row_factory = dict_factory
|
||||
return db
|
||||
|
||||
def add_cache_callback(self, key, method):
|
||||
method.callback = True
|
||||
self.cache[key] = method
|
||||
|
||||
def get_config_parameter(self, key, default):
|
||||
cfg = self.cache["config"].get(key)
|
||||
if cfg is None:
|
||||
return default
|
||||
else:
|
||||
return cfg.value
|
||||
|
||||
def emit(self, key, **kwargs):
|
||||
|
||||
if self.eventbus.get(key) is not None:
|
||||
for value in self.eventbus[key]:
|
||||
if value["async"] is False:
|
||||
value["function"](**kwargs)
|
||||
else:
|
||||
t = self.cbpi._socketio.start_background_task(target=value["function"], **kwargs)
|
||||
|
||||
def loadPlugins(self):
|
||||
for filename in os.listdir("./modules/plugins"):
|
||||
|
||||
if os.path.isdir("./modules/plugins/" + filename) is False:
|
||||
continue
|
||||
def init_kettle(self, id):
|
||||
try:
|
||||
self.modules[filename] = import_module("modules.plugins.%s" % (filename))
|
||||
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.notify("Failed to load plugin %s " % filename, str(e), type="danger", timeout=None)
|
||||
self.app.logger.error("Exception" + method.__name__ + ": " + str(e))
|
||||
self.socketio.sleep(interval)
|
||||
|
||||
|
||||
cbpi = CraftBeerPI()
|
||||
addon = cbpi.addon
|
||||
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
+1
@@ -1,4 +1,5 @@
|
||||
import sqlite3
|
||||
|
||||
from flask import json, g
|
||||
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import sqlite3
|
||||
import os
|
||||
from modules.core.core import cbpi
|
||||
from modules.core.db import get_db
|
||||
from modules import cbpi
|
||||
from db import get_db
|
||||
|
||||
def execute_file(curernt_version, data):
|
||||
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
|
||||
try:
|
||||
with sqlite3.connect("craftbeerpi.db") as conn:
|
||||
@@ -19,13 +19,13 @@ def execute_file(curernt_version, data):
|
||||
conn.commit()
|
||||
|
||||
except sqlite3.OperationalError as err:
|
||||
|
||||
print "EXCEPT"
|
||||
print err
|
||||
|
||||
@cbpi.addon.core.initializer(order=-9999)
|
||||
def init(cbpi):
|
||||
@cbpi.initalizer(order=-9999)
|
||||
def init(app=None):
|
||||
|
||||
with cbpi._app.app_context():
|
||||
with cbpi.app.app_context():
|
||||
conn = get_db()
|
||||
cur = conn.cursor()
|
||||
current_version = None
|
||||
@@ -37,8 +37,11 @@ def init(cbpi):
|
||||
pass
|
||||
result = []
|
||||
for filename in os.listdir("./update"):
|
||||
|
||||
if filename.endswith(".sql"):
|
||||
d = {"version": int(filename[:filename.index('_')]), "file": filename}
|
||||
result.append(d)
|
||||
execute_file(current_version, d)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
Executable → Regular
+22
-2
@@ -18,10 +18,9 @@ class Property(object):
|
||||
self.description = description
|
||||
|
||||
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)
|
||||
self.label = label
|
||||
self.required = required
|
||||
self.configurable = configurable
|
||||
self.default_value = default_value
|
||||
self.description = description
|
||||
@@ -46,3 +45,24 @@ class Property(object):
|
||||
self.label = label
|
||||
self.configurable = True
|
||||
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
@@ -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)
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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
+88
-35
@@ -2,12 +2,63 @@ import time
|
||||
from flask import request
|
||||
from flask_classy import route
|
||||
|
||||
from modules.core.core import cbpi
|
||||
from modules.core.db import get_db, DBModel
|
||||
from modules import DBModel, cbpi, get_db
|
||||
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):
|
||||
model = Fermenter
|
||||
cache_key = "fermenter"
|
||||
@@ -26,6 +77,7 @@ class FermenterView(BaseView):
|
||||
|
||||
def _post_put_callback(self, m):
|
||||
m.state = False
|
||||
self.reset(int(m.id))
|
||||
|
||||
@route('/<int:id>/targettemp/<temp>', methods=['POST'])
|
||||
def postTargetTemp(self, id, temp):
|
||||
@@ -35,7 +87,7 @@ class FermenterView(BaseView):
|
||||
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__)
|
||||
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)
|
||||
|
||||
@route('/<int:id>/brewname', methods=['POST'])
|
||||
@@ -44,7 +96,7 @@ class FermenterView(BaseView):
|
||||
brewname = data.get("brewname")
|
||||
cbpi.cache.get(self.cache_key)[id].brewname = brewname
|
||||
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)
|
||||
|
||||
@classmethod
|
||||
@@ -68,7 +120,7 @@ class FermenterView(BaseView):
|
||||
|
||||
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)
|
||||
|
||||
@@ -90,7 +142,7 @@ class FermenterView(BaseView):
|
||||
|
||||
FermenterStep.update(**s.__dict__)
|
||||
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)
|
||||
|
||||
@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]
|
||||
FermenterStep.delete(s.id)
|
||||
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)
|
||||
|
||||
@route('/<int:id>/start', methods=['POST'])
|
||||
@@ -128,8 +180,7 @@ class FermenterView(BaseView):
|
||||
|
||||
if inactive is not None:
|
||||
fermenter = self.get_fermenter(inactive.fermenter_id)
|
||||
|
||||
current_temp = cbpi.sensor.get_value(int(fermenter.sensor))
|
||||
current_temp = cbpi.get_sensor_value(int(fermenter.sensor))
|
||||
|
||||
inactive.state = 'A'
|
||||
inactive.start = time.time()
|
||||
@@ -140,7 +191,7 @@ class FermenterView(BaseView):
|
||||
|
||||
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)
|
||||
|
||||
@route('/<int:id>/reset', methods=["POST"])
|
||||
@@ -152,40 +203,40 @@ class FermenterView(BaseView):
|
||||
if id in cbpi.cache["fermenter_task"]:
|
||||
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)
|
||||
|
||||
@route('/<int:id>/automatic', methods=['POST'])
|
||||
def toggle(self, id):
|
||||
fermenter = cbpi.cache.get(self.cache_key)[id]
|
||||
try:
|
||||
|
||||
print fermenter.state
|
||||
if fermenter.state is False:
|
||||
# Start controller
|
||||
if fermenter.logic is not None:
|
||||
cfg = fermenter.config.copy()
|
||||
cfg.update(
|
||||
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()
|
||||
fermenter.instance = instance
|
||||
|
||||
def run(instance):
|
||||
instance.run()
|
||||
|
||||
t = cbpi._socketio.start_background_task(target=run, instance=instance)
|
||||
fermenter.state = not fermenter.state
|
||||
cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key).get(id))
|
||||
cbpi.emit("FERMENTER_CONTROLLER_STARTED", id=id)
|
||||
cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key).get(id))
|
||||
|
||||
t = cbpi.socketio.start_background_task(target=run, instance=instance)
|
||||
fermenter.state = not fermenter.state
|
||||
cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key).get(id))
|
||||
else:
|
||||
# Stop controller
|
||||
fermenter.instance.stop()
|
||||
fermenter.state = not fermenter.state
|
||||
cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key).get(id))
|
||||
cbpi.emit("FERMENTER_CONTROLLER_STOPPED", id=id)
|
||||
cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key).get(id))
|
||||
|
||||
except Exception as e:
|
||||
|
||||
print e
|
||||
cbpi.notify("Toogle Fementer Controller failed", "Pleae check the %s configuration" % fermenter.name,
|
||||
type="danger", timeout=None)
|
||||
return ('', 500)
|
||||
@@ -207,7 +258,7 @@ class FermenterView(BaseView):
|
||||
|
||||
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):
|
||||
for key, value in cbpi.cache["fermenter_task"].iteritems():
|
||||
@@ -234,33 +285,35 @@ class FermenterView(BaseView):
|
||||
pass
|
||||
|
||||
|
||||
@cbpi.addon.core.backgroundjob(key="read_target_temps_fermenter", interval=5)
|
||||
def read_target_temps(cbpi):
|
||||
@cbpi.backgroundtask(key="read_target_temps_fermenter", 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("fermenter").iteritems():
|
||||
cbpi.sensor.write_log(key, value.target_temp, prefix="fermenter")
|
||||
|
||||
cbpi.save_to_file(key, value.target_temp, prefix="fermenter")
|
||||
|
||||
|
||||
instance = FermenterView()
|
||||
|
||||
@cbpi.addon.core.backgroundjob(key="fermentation_task", interval=1)
|
||||
def execute_fermentation_step(cbpi):
|
||||
with cbpi._app.app_context():
|
||||
@cbpi.backgroundtask(key="fermentation_task", interval=1)
|
||||
def execute_fermentation_step(api):
|
||||
with cbpi.app.app_context():
|
||||
instance.check_step()
|
||||
|
||||
|
||||
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.addon.core.initializer(order=1)
|
||||
@cbpi.initalizer(order=1)
|
||||
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()
|
||||
|
||||
Executable → Regular
+47
-185
@@ -1,145 +1,19 @@
|
||||
from flask import request
|
||||
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.db import DBModel
|
||||
from modules.database.dbmodel import Kettle
|
||||
|
||||
class KettleView(BaseView):
|
||||
class Kettle(DBModel):
|
||||
__fields__ = ["name","sensor", "heater", "automatic", "logic", "config", "agitator", "target_temp"]
|
||||
__table_name__ = "kettle"
|
||||
__json_fields__ = ["config"]
|
||||
|
||||
|
||||
class Kettle2View(BaseView):
|
||||
model = Kettle
|
||||
cache_key = "kettle"
|
||||
|
||||
@route('/', methods=["GET"])
|
||||
def getAll(self):
|
||||
"""
|
||||
Get all Kettles
|
||||
---
|
||||
tags:
|
||||
- kettle
|
||||
responses:
|
||||
200:
|
||||
description: List auf all Kettles
|
||||
"""
|
||||
return super(KettleView, self).getAll()
|
||||
|
||||
@route('/', methods=["POST"])
|
||||
def post(self):
|
||||
"""
|
||||
Create a new kettle
|
||||
---
|
||||
tags:
|
||||
- kettle
|
||||
parameters:
|
||||
- in: body
|
||||
name: body
|
||||
schema:
|
||||
id: Kettle
|
||||
required:
|
||||
- name
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: name for user
|
||||
sensor:
|
||||
type: string
|
||||
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).post()
|
||||
|
||||
|
||||
@route('/<int:id>', methods=["PUT"])
|
||||
def put(self, id):
|
||||
"""
|
||||
Update a kettle
|
||||
---
|
||||
tags:
|
||||
- kettle
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
schema:
|
||||
type: integer
|
||||
required: true
|
||||
description: Numeric ID of the Kettle
|
||||
- in: body
|
||||
name: body
|
||||
schema:
|
||||
id: Kettle
|
||||
required:
|
||||
- name
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: name for user
|
||||
sensor:
|
||||
type: string
|
||||
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
|
||||
@@ -163,61 +37,49 @@ class KettleView(BaseView):
|
||||
|
||||
@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)
|
||||
cbpi.cache.get("kettle")[id].target_temp = float(temp)
|
||||
self.model.update(**self.api.cache.get(self.cache_key)[id].__dict__)
|
||||
cbpi.emit("UPDATE_KETTLE_TARGET_TEMP", {"id": id, "target_temp": temp})
|
||||
return ('', 204)
|
||||
|
||||
@route('/<int:id>/automatic', methods=['POST'])
|
||||
def toggle(self, id):
|
||||
kettle = cbpi.cache.get("kettle")[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)
|
||||
if kettle.state is False:
|
||||
# Start controller
|
||||
if kettle.logic is not None:
|
||||
cfg = kettle.config.copy()
|
||||
cfg.update(dict(api=cbpi, kettle_id=kettle.id, heater=kettle.heater, sensor=kettle.sensor))
|
||||
instance = cbpi.get_controller(kettle.logic).get("class")(**cfg)
|
||||
instance.init()
|
||||
kettle.instance = instance
|
||||
def run(instance):
|
||||
instance.run()
|
||||
t = self.api.socketio.start_background_task(target=run, instance=instance)
|
||||
kettle.state = not kettle.state
|
||||
cbpi.emit("UPDATE_KETTLE", cbpi.cache.get("kettle").get(id))
|
||||
else:
|
||||
# Stop controller
|
||||
kettle.instance.stop()
|
||||
kettle.state = not kettle.state
|
||||
cbpi.emit("UPDATE_KETTLE", cbpi.cache.get("kettle").get(id))
|
||||
return ('', 204)
|
||||
|
||||
@cbpi.addon.core.backgroundjob(key="read_target_temps", interval=5)
|
||||
@cbpi.event("SET_TARGET_TEMP")
|
||||
def set_target_temp(id, temp):
|
||||
'''
|
||||
Change Taget Temp Event
|
||||
:param id: kettle id
|
||||
:param temp: target temp to set
|
||||
:return: None
|
||||
'''
|
||||
|
||||
Kettle2View().postTargetTemp(id,temp)
|
||||
|
||||
@cbpi.backgroundtask(key="read_target_temps", interval=5)
|
||||
def read_target_temps(api):
|
||||
"""
|
||||
background process that reads all passive sensors in interval of 1 second
|
||||
@@ -225,10 +87,10 @@ def read_target_temps(api):
|
||||
"""
|
||||
result = {}
|
||||
for key, value in cbpi.cache.get("kettle").iteritems():
|
||||
cbpi.sensor.write_log(key, value.target_temp, prefix="kettle")
|
||||
cbpi.save_to_file(key, value.target_temp, prefix="kettle")
|
||||
|
||||
@cbpi.addon.core.initializer()
|
||||
@cbpi.initalizer()
|
||||
def init(cbpi):
|
||||
KettleView.api = cbpi
|
||||
KettleView.register(cbpi._app, route_base='/api/kettle')
|
||||
KettleView.init_cache()
|
||||
Kettle2View.api = cbpi
|
||||
Kettle2View.register(cbpi.app,route_base='/api/kettle')
|
||||
Kettle2View.init_cache()
|
||||
|
||||
@@ -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
@@ -1,175 +1 @@
|
||||
import datetime
|
||||
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')
|
||||
import endpoints
|
||||
@@ -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')
|
||||
@@ -1,46 +1,30 @@
|
||||
import json
|
||||
from flask_classy import FlaskView, route
|
||||
from modules.core.core import cbpi
|
||||
|
||||
from modules import cbpi
|
||||
class NotificationView(FlaskView):
|
||||
|
||||
@route('/', methods=['GET'])
|
||||
def getMessages(self):
|
||||
"""
|
||||
Get all Messages
|
||||
---
|
||||
tags:
|
||||
- notification
|
||||
responses:
|
||||
200:
|
||||
description: All messages
|
||||
Get all messages
|
||||
:return: current messages
|
||||
"""
|
||||
|
||||
return json.dumps(cbpi.cache["messages"])
|
||||
|
||||
@route('/<id>', methods=['DELETE'])
|
||||
def dismiss(self, id):
|
||||
"""
|
||||
Delete Message
|
||||
---
|
||||
tags:
|
||||
- notification
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
schema:
|
||||
type: integer
|
||||
required: true
|
||||
description: Numeric ID of the message
|
||||
responses:
|
||||
200:
|
||||
description: Message deleted
|
||||
Delete message from cache by id
|
||||
:param id: message id to be deleted
|
||||
:return: empty response HTTP 204
|
||||
"""
|
||||
for idx, m in enumerate(cbpi.cache.get("messages", [])):
|
||||
if (m.get("id") == id):
|
||||
cbpi.cache["messages"].pop(idx)
|
||||
return ('', 204)
|
||||
|
||||
#@cbpi.event("MESSAGE", async=True)
|
||||
@cbpi.event("MESSAGE", async=True)
|
||||
def messageEvent(message, **kwargs):
|
||||
"""
|
||||
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.emit("NOTIFY", message)
|
||||
|
||||
@cbpi.addon.core.initializer(order=2)
|
||||
@cbpi.initalizer(order=2)
|
||||
def init(cbpi):
|
||||
"""
|
||||
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}
|
||||
cbpi.cache["messages"].append(msg)
|
||||
|
||||
NotificationView.register(cbpi._app, route_base='/api/notification')
|
||||
NotificationView.register(cbpi.app, route_base='/api/notification')
|
||||
|
||||
@@ -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')
|
||||
@@ -2,28 +2,18 @@ from flask import json, request
|
||||
from flask_classy import FlaskView, route
|
||||
from git import Repo, Git
|
||||
import sqlite3
|
||||
from modules.core.core import cbpi
|
||||
from modules.app_config import cbpi
|
||||
from werkzeug.utils import secure_filename
|
||||
import pprint
|
||||
import time
|
||||
import os
|
||||
from modules.step import Step,StepView
|
||||
from modules.steps import Step,StepView
|
||||
import xml.etree.ElementTree
|
||||
|
||||
class BeerXMLImport(FlaskView):
|
||||
BEER_XML_FILE = "./upload/beer.xml"
|
||||
@route('/', methods=['GET'])
|
||||
def get(self):
|
||||
|
||||
"""
|
||||
Get BeerXML
|
||||
---
|
||||
tags:
|
||||
- beerxml
|
||||
responses:
|
||||
200:
|
||||
description: BeerXML file stored in CraftBeerPI
|
||||
"""
|
||||
if not os.path.exists(self.BEER_XML_FILE):
|
||||
self.api.notify(headline="File Not Found", message="Please upload a Beer.xml File",
|
||||
type="danger")
|
||||
@@ -41,16 +31,6 @@ class BeerXMLImport(FlaskView):
|
||||
|
||||
@route('/upload', methods=['POST'])
|
||||
def upload_file(self):
|
||||
"""
|
||||
Upload BeerXML File
|
||||
---
|
||||
tags:
|
||||
- beerxml
|
||||
|
||||
responses:
|
||||
200:
|
||||
description: BeerXML File Uploaded
|
||||
"""
|
||||
try:
|
||||
if request.method == 'POST':
|
||||
file = request.files['file']
|
||||
@@ -66,25 +46,9 @@ class BeerXMLImport(FlaskView):
|
||||
@route('/<int:id>', methods=['POST'])
|
||||
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)
|
||||
boil_time_alerts = self.getBoilAlerts(id)
|
||||
name = self.getRecipeName(id)
|
||||
self.api.set_config_parameter("brew_name", name)
|
||||
boil_time = self.getBoilTime(id)
|
||||
@@ -104,10 +68,29 @@ class BeerXMLImport(FlaskView):
|
||||
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": "ChilStep", "type": "ChilStep", "config": {"timer": 15}})
|
||||
## Add cooking step
|
||||
Step.insert(**{"name": "Boil", "type": boilstep_type, "config": {"kettle": boil_kettle, "temp": boil_temp, "timer": boil_time}})
|
||||
## Add boiling step
|
||||
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
|
||||
Step.insert(**{"name": "Whirlpool", "type": "ChilStep", "config": {"timer": 15}})
|
||||
StepView().reset()
|
||||
self.api.emit("UPDATE_ALL_STEPS", Step.get_all())
|
||||
self.api.notify(headline="Recipe %s loaded successfully" % name, message="")
|
||||
except Exception as e:
|
||||
@@ -124,24 +107,46 @@ class BeerXMLImport(FlaskView):
|
||||
e = xml.etree.ElementTree.parse(self.BEER_XML_FILE).getroot()
|
||||
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):
|
||||
|
||||
|
||||
|
||||
e = xml.etree.ElementTree.parse(self.BEER_XML_FILE).getroot()
|
||||
steps = []
|
||||
for e in e.findall('./RECIPE[%s]/MASH/MASH_STEPS/MASH_STEP' % (str(id))):
|
||||
|
||||
if self.api.get_config_parameter("unit", "C") == "C":
|
||||
temp = float(e.find("STEP_TEMP").text)
|
||||
else:
|
||||
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)})
|
||||
|
||||
return steps
|
||||
|
||||
@cbpi.addon.core.initializer()
|
||||
@cbpi.initalizer()
|
||||
def init(cbpi):
|
||||
|
||||
BeerXMLImport.api = cbpi
|
||||
BeerXMLImport.register(cbpi._app, route_base='/api/beerxml')
|
||||
BeerXMLImport.register(cbpi.app, route_base='/api/beerxml')
|
||||
|
||||
@@ -2,28 +2,18 @@ from flask import json, request
|
||||
from flask_classy import FlaskView, route
|
||||
from git import Repo, Git
|
||||
import sqlite3
|
||||
from modules.core.core import cbpi
|
||||
from modules.app_config import cbpi
|
||||
from werkzeug.utils import secure_filename
|
||||
import pprint
|
||||
import time
|
||||
import os
|
||||
from modules.step import Step, StepView
|
||||
from modules.steps import Step, StepView
|
||||
|
||||
|
||||
class KBH(FlaskView):
|
||||
|
||||
@route('/', methods=['GET'])
|
||||
def get(self):
|
||||
|
||||
"""
|
||||
Get all recipes from uploaded kleinerbrauhelfer database
|
||||
---
|
||||
tags:
|
||||
- kleinerbrauhelfer
|
||||
responses:
|
||||
200:
|
||||
description: Recipes from kleinerbrauhelfer database
|
||||
"""
|
||||
conn = None
|
||||
try:
|
||||
if not os.path.exists(self.api.app.config['UPLOAD_FOLDER'] + '/kbh.db'):
|
||||
@@ -51,16 +41,6 @@ class KBH(FlaskView):
|
||||
|
||||
@route('/upload', methods=['POST'])
|
||||
def upload_file(self):
|
||||
"""
|
||||
Upload KleinerBrauhelfer Database File
|
||||
---
|
||||
tags:
|
||||
- kleinerbrauhelfer
|
||||
|
||||
responses:
|
||||
200:
|
||||
description: File uploaed
|
||||
"""
|
||||
try:
|
||||
if request.method == 'POST':
|
||||
file = request.files['file']
|
||||
@@ -77,23 +57,6 @@ class KBH(FlaskView):
|
||||
|
||||
@route('/<int:id>', methods=['POST'])
|
||||
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")
|
||||
mashinstep_type = cbpi.get_config_parameter("step_mashin", "MashInStep")
|
||||
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):
|
||||
|
||||
KBH.api = cbpi
|
||||
KBH.register(cbpi._app, route_base='/api/kbh')
|
||||
KBH.register(cbpi.app, route_base='/api/kbh')
|
||||
|
||||
@@ -2,12 +2,12 @@ from flask import json, request
|
||||
from flask_classy import FlaskView, route
|
||||
from git import Repo, Git
|
||||
import sqlite3
|
||||
from modules.core.core import cbpi
|
||||
from modules.app_config import cbpi
|
||||
from werkzeug.utils import secure_filename
|
||||
import pprint
|
||||
import time
|
||||
import os
|
||||
from modules.step import Step,StepView
|
||||
from modules.steps import Step,StepView
|
||||
import xml.etree.ElementTree
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ class RESTImport(FlaskView):
|
||||
return ('', 204)
|
||||
|
||||
|
||||
@cbpi.addon.core.initializer()
|
||||
@cbpi.initalizer()
|
||||
def init(cbpi):
|
||||
RESTImport.api = cbpi
|
||||
RESTImport.register(cbpi._app, route_base='/api/recipe/import/v1')
|
||||
RESTImport.register(cbpi.app, route_base='/api/recipe/import/v1')
|
||||
|
||||
@@ -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()
|
||||
Executable
+48
@@ -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
+3
-2
@@ -19,9 +19,10 @@ def sendStats(cbpi):
|
||||
|
||||
try:
|
||||
serial = getserial()
|
||||
|
||||
info = {
|
||||
"id": serial,
|
||||
"version": "3.1",
|
||||
"version": "3.0",
|
||||
"kettle": len(cbpi.cache.get("kettle")),
|
||||
"hardware": len(cbpi.cache.get("actors")),
|
||||
"thermometer": "CBP3.0",
|
||||
@@ -29,7 +30,7 @@ def sendStats(cbpi):
|
||||
}
|
||||
|
||||
import requests
|
||||
#r = requests.post('http://statistics.craftbeerpi.com', json=info)
|
||||
r = requests.post('http://statistics.craftbeerpi.com', json=info)
|
||||
|
||||
except Exception as e:
|
||||
pass
|
||||
@@ -2,10 +2,66 @@ import time
|
||||
from flask import json, request
|
||||
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.core import cbpi
|
||||
from modules.database.dbmodel import Step
|
||||
|
||||
|
||||
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 StepView(BaseView):
|
||||
@@ -17,66 +73,26 @@ class StepView(BaseView):
|
||||
|
||||
@route('/sort', methods=["POST"])
|
||||
def sort_steps(self):
|
||||
"""
|
||||
Sort all steps
|
||||
---
|
||||
tags:
|
||||
- steps
|
||||
responses:
|
||||
204:
|
||||
description: Steps sorted. Update delivered via web socket
|
||||
"""
|
||||
Step.sort(request.json)
|
||||
cbpi.ws_emit("UPDATE_ALL_STEPS", self.model.get_all())
|
||||
cbpi.emit("UPDATE_ALL_STEPS", self.model.get_all())
|
||||
return ('', 204)
|
||||
|
||||
@route('/', methods=["DELETE"])
|
||||
def deleteAll(self):
|
||||
"""
|
||||
Delete all Steps
|
||||
---
|
||||
tags:
|
||||
- steps
|
||||
responses:
|
||||
204:
|
||||
description: All steps deleted
|
||||
"""
|
||||
self.model.delete_all()
|
||||
self.api.emit("ALL_BREWING_STEPS_DELETED")
|
||||
cbpi.ws_emit("UPDATE_ALL_STEPS", self.model.get_all())
|
||||
cbpi.emit("UPDATE_ALL_STEPS", self.model.get_all())
|
||||
return ('', 204)
|
||||
|
||||
@route('/action/<method>', methods=["POST"])
|
||||
def action(self, method):
|
||||
"""
|
||||
Call Step Action
|
||||
---
|
||||
tags:
|
||||
- steps
|
||||
responses:
|
||||
204:
|
||||
description: Step action called
|
||||
"""
|
||||
self.api.emit("BREWING_STEP_ACTION_INVOKED", method=method)
|
||||
cbpi.cache["active_step"].__getattribute__(method)()
|
||||
return ('', 204)
|
||||
|
||||
@route('/reset', methods=["POST"])
|
||||
def reset(self):
|
||||
|
||||
"""
|
||||
Reset All Steps
|
||||
---
|
||||
tags:
|
||||
- steps
|
||||
responses:
|
||||
200:
|
||||
description: Steps reseted
|
||||
"""
|
||||
self.model.reset_all_steps()
|
||||
self.stop_step()
|
||||
self.api.emit("ALL_BREWING_STEPS_RESET")
|
||||
cbpi.ws_emit("UPDATE_ALL_STEPS", self.model.get_all())
|
||||
cbpi.emit("UPDATE_ALL_STEPS", self.model.get_all())
|
||||
return ('', 204)
|
||||
|
||||
def stop_step(self):
|
||||
@@ -86,21 +102,16 @@ class StepView(BaseView):
|
||||
'''
|
||||
step = cbpi.cache.get("active_step")
|
||||
cbpi.cache["active_step"] = None
|
||||
self.api.emit("BREWING_STEPS_STOP")
|
||||
|
||||
if step is not None:
|
||||
step.finish()
|
||||
|
||||
@route('/reset/current', methods=['POST'])
|
||||
def resetCurrentStep(self):
|
||||
"""
|
||||
Reset current Steps
|
||||
---
|
||||
tags:
|
||||
- steps
|
||||
responses:
|
||||
200:
|
||||
description: Current Steps reseted
|
||||
"""
|
||||
'''
|
||||
Reset current step
|
||||
:return:
|
||||
'''
|
||||
step = cbpi.cache.get("active_step")
|
||||
|
||||
if step is not None:
|
||||
@@ -112,13 +123,11 @@ class StepView(BaseView):
|
||||
state[field] = step.__getattribute__(field)
|
||||
Step.update_step_state(step.id, state)
|
||||
step.reset_dirty()
|
||||
self.api.emit("BREWING_STEPS_RESET_CURRENT")
|
||||
cbpi.ws_emit("UPDATE_ALL_STEPS", self.model.get_all())
|
||||
cbpi.emit("UPDATE_ALL_STEPS", self.model.get_all())
|
||||
return ('', 204)
|
||||
|
||||
def init_step(self, step):
|
||||
|
||||
cbpi.brewing.log_action("Start Step %s" % step.name)
|
||||
cbpi.log_action("Start Step %s" % step.name)
|
||||
type_cfg = cbpi.cache.get("step_types").get(step.type)
|
||||
if type_cfg is None:
|
||||
# if type not found
|
||||
@@ -136,16 +145,6 @@ class StepView(BaseView):
|
||||
@route('/next', methods=['POST'])
|
||||
@route('/start', methods=['POST'])
|
||||
def start(self):
|
||||
|
||||
"""
|
||||
Next Step
|
||||
---
|
||||
tags:
|
||||
- steps
|
||||
responses:
|
||||
200:
|
||||
description: Next Step
|
||||
"""
|
||||
active = Step.get_by_state("A")
|
||||
inactive = Step.get_by_state('I')
|
||||
|
||||
@@ -154,7 +153,6 @@ class StepView(BaseView):
|
||||
active.end = int(time.time())
|
||||
self.stop_step()
|
||||
Step.update(**active.__dict__)
|
||||
self.api.emit("BREWING_STEP_DONE")
|
||||
|
||||
if (inactive is not None):
|
||||
self.init_step(inactive)
|
||||
@@ -162,15 +160,11 @@ class StepView(BaseView):
|
||||
inactive.stepstate = inactive.config
|
||||
inactive.start = int(time.time())
|
||||
Step.update(**inactive.__dict__)
|
||||
self.api.emit("BREWING_STEP_STARTED")
|
||||
else:
|
||||
cbpi.brewing.log_action("Brewing Finished")
|
||||
self.api.emit("BREWING_FINISHED")
|
||||
cbpi.log_action("Brewing Finished")
|
||||
cbpi.notify("Brewing Finished", "You are done!", timeout=None)
|
||||
|
||||
|
||||
cbpi.ws_emit("UPDATE_ALL_STEPS", Step.get_all())
|
||||
|
||||
cbpi.emit("UPDATE_ALL_STEPS", Step.get_all())
|
||||
return ('', 204)
|
||||
|
||||
def get_manged_fields_as_array(type_cfg):
|
||||
@@ -182,63 +176,64 @@ def get_manged_fields_as_array(type_cfg):
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@cbpi.try_catch(None)
|
||||
def init_after_startup():
|
||||
'''
|
||||
Restart after startup. Check is a step is in state A and reinitialize
|
||||
:return: None
|
||||
'''
|
||||
|
||||
step = Step.get_by_state('A')
|
||||
# We have an active step
|
||||
if step is not None:
|
||||
|
||||
# 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
|
||||
return
|
||||
|
||||
cfg = step.stepstate.copy()
|
||||
cfg.update(dict(api=cbpi, id=step.id, managed_fields=get_manged_fields_as_array(type_cfg)))
|
||||
instance = type_cfg.get("class")(**cfg)
|
||||
instance.init()
|
||||
cbpi.cache["active_step"] = instance
|
||||
|
||||
@cbpi.addon.core.initializer(order=2000)
|
||||
@cbpi.initalizer(order=2000)
|
||||
def init(cbpi):
|
||||
|
||||
StepView.register(cbpi._app, route_base='/api/step')
|
||||
StepView.register(cbpi.app, route_base='/api/step')
|
||||
|
||||
def get_all():
|
||||
with cbpi._app.app_context():
|
||||
with cbpi.app.app_context():
|
||||
return Step.get_all()
|
||||
|
||||
with cbpi._app.app_context():
|
||||
with cbpi.app.app_context():
|
||||
init_after_startup()
|
||||
|
||||
cbpi.add_cache_callback("steps", get_all)
|
||||
|
||||
@cbpi.addon.core.backgroundjob(key="step_task", interval=0.1)
|
||||
@cbpi.backgroundtask(key="step_task", interval=0.1)
|
||||
def execute_step(api):
|
||||
'''
|
||||
Background job which executes the step
|
||||
:return:
|
||||
'''
|
||||
with cbpi._app.app_context():
|
||||
with cbpi.app.app_context():
|
||||
step = cbpi.cache.get("active_step")
|
||||
if step is not None:
|
||||
step.execute()
|
||||
|
||||
if step.is_dirty():
|
||||
|
||||
state = {}
|
||||
for field in step.managed_fields:
|
||||
state[field] = step.__getattribute__(field)
|
||||
|
||||
Step.update_step_state(step.id, state)
|
||||
step.reset_dirty()
|
||||
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())
|
||||
cbpi.emit("UPDATE_ALL_STEPS", Step.get_all())
|
||||
+1
-193
@@ -1,193 +1 @@
|
||||
import flask_login
|
||||
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')
|
||||
import endpoints
|
||||
Executable
+145
@@ -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
@@ -1,32 +1 @@
|
||||
from flask import Blueprint,render_template
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
import endpoints
|
||||
@@ -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")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
}
|
||||
|
||||
.container-fluid {
|
||||
|
||||
padding-right: 5px;
|
||||
padding-left: 5px;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
body {
|
||||
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
+44
-44
File diff suppressed because one or more lines are too long
@@ -17,6 +17,5 @@
|
||||
<div id="root" ></div>
|
||||
<script src="static/bundle.js" type="text/javascript"></script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Executable → Regular
+4
-6
@@ -1,15 +1,13 @@
|
||||
Flask==0.11.1
|
||||
Flask==0.12.4
|
||||
Flask-SocketIO==2.6.2
|
||||
flask_login==0.4.0
|
||||
flask_swagger==0.2.13
|
||||
eventlet==0.19.0
|
||||
eventlet==0.31.0
|
||||
greenlet==0.4.10
|
||||
python-dateutil==2.5.3
|
||||
python-engineio==0.9.2
|
||||
python-mimeparse==1.5.2
|
||||
python-socketio==1.4.4
|
||||
PyYAML==3.11
|
||||
requests==2.11.0
|
||||
PyYAML==4.2b1
|
||||
requests==2.20.0
|
||||
Werkzeug==0.11.10
|
||||
httplib2==0.9.2
|
||||
flask-classy==0.6.10
|
||||
|
||||
@@ -1,33 +1,11 @@
|
||||
#!/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()
|
||||
|
||||
@@ -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"]');
|
||||
Reference in New Issue
Block a user