mirror of
https://github.com/Manuel83/craftbeerpi3
synced 2026-08-09 18:52:40 +02:00
Compare commits
150
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5098f74946 | ||
|
|
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 | ||
|
|
bf3fbddbe2 | ||
|
|
590ef4ca92 | ||
|
|
d3bc54001f | ||
|
|
25dead2e4d | ||
|
|
f771289224 | ||
|
|
bf02c08677 | ||
|
|
c765589984 | ||
|
|
ae7e3a2e7b | ||
|
|
3eee952a04 | ||
|
|
88b360a8d7 | ||
|
|
f62f9ba7ce | ||
|
|
909a6aa0ea | ||
|
|
40b7253a6d | ||
|
|
3761713219 | ||
|
|
a96c678dd9 | ||
|
|
e7765a2c4c | ||
|
|
2fe3d1d68e | ||
|
|
1f1f84018d | ||
|
|
f68e0f46ee | ||
|
|
b75926d23e | ||
|
|
aa3420efc8 | ||
|
|
124a03bd0f | ||
|
|
1c4e16e358 | ||
|
|
74b56937e7 | ||
|
|
805b3fae1b | ||
|
|
f8d4015b6e | ||
|
|
01997f741c | ||
|
|
1184107e50 | ||
|
|
ea793996ab | ||
|
|
bbcf0a40bf | ||
|
|
d6d023798a | ||
|
|
518a139b5d | ||
|
|
a97c5f4585 | ||
|
|
9c44d3ff62 | ||
|
|
544e9bb72a | ||
|
|
eee5680ba9 | ||
|
|
42e91de9eb | ||
|
|
3c541e0946 | ||
|
|
4d08b17abd | ||
|
|
be8506d95b | ||
|
|
98fbcaf910 | ||
|
|
b40e82a6bb | ||
|
|
a368213cd1 | ||
|
|
58fd9a659e | ||
|
|
e750fbb70e | ||
|
|
ed9bbed084 | ||
|
|
15d7906850 | ||
|
|
b9b3fee8c9 | ||
|
|
ad02e2490d | ||
|
|
eb715fabb6 | ||
|
|
5d6e32a57f | ||
|
|
85f0c6cbff | ||
|
|
f60658c84e | ||
|
|
b4ddc60c07 | ||
|
|
6fd39a81a9 | ||
|
|
bb6f6f2b36 | ||
|
|
ca9575ac29 | ||
|
|
9a1981fa46 | ||
|
|
46fc39c185 | ||
|
|
54dd31ea11 | ||
|
|
1d586b353e | ||
|
|
fc04ae01a7 | ||
|
|
929db1df81 | ||
|
|
fd5ac5ce77 | ||
|
|
91c0dc9a59 | ||
|
|
8c0f03c139 | ||
|
|
2398ce360d | ||
|
|
785e5c984c | ||
|
|
a978f43649 | ||
|
|
da6b107e94 | ||
|
|
47268d8d1c | ||
|
|
8916cd6054 | ||
|
|
ca7270b5ee | ||
|
|
2c941e01d5 | ||
|
|
7591d47add | ||
|
|
20b535b293 | ||
|
|
3af76d0a1e | ||
|
|
62f31e2dd4 | ||
|
|
88a9989c13 | ||
|
|
ba0e932239 | ||
|
|
734f0c6693 | ||
|
|
45c937825b | ||
|
|
291f09dcd1 | ||
|
|
e7de4d8965 | ||
|
|
c2aa5d8141 | ||
|
|
dc046e92c4 | ||
|
|
4470767a1c | ||
|
|
e67baeabb0 | ||
|
|
7d35538962 | ||
|
|
10a989cee9 | ||
|
|
3396e1b379 | ||
|
|
0749c55478 | ||
|
|
3b60c8cfd1 | ||
|
|
ef0f719c58 | ||
|
|
1ec6b9acf1 | ||
|
|
fc945f093c | ||
|
|
ebeb726dd9 | ||
|
|
00739ea38f | ||
|
|
71449740fa | ||
|
|
f1ec635f25 | ||
|
|
139f5e8f91 | ||
|
|
7594aecaee | ||
|
|
f0b7c6e640 | ||
|
|
26da7c66bc | ||
|
|
167a0bb8f8 | ||
|
|
c77fff6ffa |
@@ -8,3 +8,12 @@ craftbeerpi.db
|
||||
modules/plugins/*
|
||||
!modules/plugins/__init__.py
|
||||
*.pyc
|
||||
*.js
|
||||
*.css
|
||||
modules/ui/package.json
|
||||
|
||||
modules/ui/.babelrc
|
||||
yarn.lock
|
||||
|
||||
|
||||
modules/ui/package-lock.json
|
||||
|
||||
+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"]
|
||||
@@ -18,10 +18,31 @@ Type <code>cd craftbeerpi3</code> to navigate into the craftbeerpi folder.
|
||||
|
||||
Type <code>sudo ./install.sh</code>
|
||||
|
||||
## Hardware Wiring
|
||||
|
||||
Here you will find a guide how to wire everything up.
|
||||
|
||||
http://web.craftbeerpi.com/hardware/
|
||||
|
||||
## ATTENTION
|
||||
|
||||
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:
|
||||
|
||||
+18
-14
@@ -1,4 +1,4 @@
|
||||
CREATE TABLE kettle
|
||||
CREATE TABLE IF NOT EXISTS kettle
|
||||
(
|
||||
id INTEGER PRIMARY KEY NOT NULL,
|
||||
name VARCHAR(80),
|
||||
@@ -13,7 +13,7 @@ CREATE TABLE kettle
|
||||
diameter INTEGER
|
||||
);
|
||||
|
||||
CREATE TABLE step
|
||||
CREATE TABLE IF NOT EXISTS step
|
||||
(
|
||||
id INTEGER PRIMARY KEY NOT NULL,
|
||||
"order" INTEGER,
|
||||
@@ -27,7 +27,7 @@ CREATE TABLE step
|
||||
kettleid INTEGER
|
||||
);
|
||||
|
||||
CREATE TABLE sensor
|
||||
CREATE TABLE IF NOT EXISTS sensor
|
||||
(
|
||||
id INTEGER PRIMARY KEY NOT NULL,
|
||||
type VARCHAR(100),
|
||||
@@ -36,7 +36,7 @@ CREATE TABLE sensor
|
||||
hide BOOLEAN
|
||||
);
|
||||
|
||||
CREATE TABLE fermenter_step
|
||||
CREATE TABLE IF NOT EXISTS fermenter_step
|
||||
(
|
||||
id INTEGER PRIMARY KEY NOT NULL,
|
||||
name VARCHAR(80),
|
||||
@@ -54,7 +54,7 @@ CREATE TABLE fermenter_step
|
||||
FOREIGN KEY (fermenter_id) REFERENCES fermenter (id)
|
||||
);
|
||||
|
||||
CREATE TABLE fermenter
|
||||
CREATE TABLE IF NOT EXISTS fermenter
|
||||
(
|
||||
id INTEGER PRIMARY KEY NOT NULL,
|
||||
name VARCHAR(80),
|
||||
@@ -69,7 +69,7 @@ CREATE TABLE fermenter
|
||||
target_temp INTEGER
|
||||
);
|
||||
|
||||
CREATE TABLE config
|
||||
CREATE TABLE IF NOT EXISTS config
|
||||
(
|
||||
name VARCHAR(50) PRIMARY KEY NOT NULL,
|
||||
value VARCHAR(255),
|
||||
@@ -79,15 +79,19 @@ CREATE TABLE config
|
||||
);
|
||||
|
||||
|
||||
INSERT INTO config VALUES ('kettle_cols', 4, 'select', 'Adjust the width of a kettle widget on the brewing dashboard', '[1,2,3, 4, 5, 6, 7, 8, 9, 10, 11, 12]');
|
||||
INSERT INTO config VALUES ('actor_cols', 4, 'select', 'Adjust the width of a actor widget on the brewing dashboard', '[1,2,3, 4, 5, 6, 7, 8, 9, 10, 11, 12]');
|
||||
INSERT 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 INTO config VALUES ('unit', 'C', 'select', 'Temperature Unit', '["C","F"]');
|
||||
INSERT INTO config VALUES ('brewery_name', 'My Home Brewery', 'text', 'Your brewery name', NULL );
|
||||
INSERT INTO config VALUES ('buzzer', 16, 'select', 'Buzzer GPIO', '[16,17,18,19,20]');
|
||||
INSERT INTO config VALUES ('setup', 'YES', 'select', 'Show the Setup dialog', '["YES","NO"]');
|
||||
INSERT OR IGNORE INTO config VALUES ('kettle_cols', 4, 'select', 'Adjust the width of a kettle widget on the brewing dashboard', '[1,2,3, 4, 5, 6, 7, 8, 9, 10, 11, 12]');
|
||||
INSERT OR IGNORE INTO config VALUES ('actor_cols', 4, 'select', 'Adjust the width of a actor widget on the brewing dashboard', '[1,2,3, 4, 5, 6, 7, 8, 9, 10, 11, 12]');
|
||||
INSERT OR IGNORE INTO config VALUES ('sensor_cols', 4, 'select', 'Adjust the width of a sensor widget on the brewing dashboard', '[1,2,3, 4, 5, 6, 7, 8, 9, 10, 11, 12]');
|
||||
INSERT OR IGNORE INTO config VALUES ('unit', 'C', 'select', 'Temperature Unit', '["C","F"]');
|
||||
INSERT OR IGNORE INTO config VALUES ('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,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"]');
|
||||
|
||||
CREATE TABLE actor
|
||||
|
||||
CREATE TABLE IF NOT EXISTS actor
|
||||
(
|
||||
id INTEGER PRIMARY KEY NOT NULL,
|
||||
name VARCHAR(50),
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
3.0.2
|
||||
@@ -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
|
||||
+14
-10
@@ -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
|
||||
@@ -136,13 +129,24 @@ show_menu () {
|
||||
fi
|
||||
;;
|
||||
10)
|
||||
confirmAnswer "Are you sure you want to reboo the Raspberry Pi?"
|
||||
confirmAnswer "Are you sure you want to reboot the Raspberry Pi?"
|
||||
if [ $? = 0 ]; then
|
||||
sudo reboot
|
||||
else
|
||||
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
|
||||
}
|
||||
|
||||
+8
-1
@@ -29,12 +29,15 @@ import modules.sensors
|
||||
import modules.actor
|
||||
import modules.notification
|
||||
import modules.fermenter
|
||||
import modules.addon
|
||||
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
|
||||
@@ -57,9 +60,13 @@ def init_db():
|
||||
pass
|
||||
|
||||
init_db()
|
||||
initPlugins()
|
||||
cbpi.run_init()
|
||||
|
||||
cbpi.run_background_processes()
|
||||
|
||||
|
||||
|
||||
app.logger.info("##########################################")
|
||||
app.logger.info("### STARTUP COMPLETE")
|
||||
app.logger.info("##########################################")
|
||||
@@ -17,10 +17,10 @@ class ActorView(BaseView):
|
||||
obj.state = 0
|
||||
obj.power = 100
|
||||
|
||||
def post_post_callback(self, m):
|
||||
def _post_post_callback(self, m):
|
||||
self.api.init_actor(m.id)
|
||||
|
||||
def post_put_callback(self, m):
|
||||
def _post_put_callback(self, m):
|
||||
|
||||
self.api.init_actor(m.id)
|
||||
|
||||
@@ -60,10 +60,15 @@ class ActorView(BaseView):
|
||||
t = self.api.socketio.start_background_task(target=self.toggleTimeJob, id=id, t=t)
|
||||
return ('', 204)
|
||||
|
||||
@route('<int:id>/action/<method>', methods=["POST"])
|
||||
def action(self, id, method):
|
||||
|
||||
cbpi.cache.get("actors").get(id).instance.__getattribute__(method)()
|
||||
return ('', 204)
|
||||
|
||||
|
||||
@cbpi.initalizer(order=1000)
|
||||
def init(cbpi):
|
||||
print "INITIALIZE ACTOR MODULE"
|
||||
cbpi.app.logger.info("INITIALIZE ACTOR MODULE")
|
||||
ActorView.register(cbpi.app, route_base='/api/actor')
|
||||
ActorView.init_cache()
|
||||
cbpi.init_actors()
|
||||
@@ -166,7 +166,9 @@ def update_addon(name):
|
||||
|
||||
def loadCorePlugins():
|
||||
for filename in os.listdir("./modules/base_plugins"):
|
||||
if filename.endswith(".py") or filename.endswith(".pyc"):
|
||||
|
||||
|
||||
if os.path.isdir("./modules/base_plugins/"+filename) is False:
|
||||
continue
|
||||
try:
|
||||
modules[filename] = import_module("modules.base_plugins.%s" % (filename))
|
||||
@@ -178,7 +180,7 @@ def loadCorePlugins():
|
||||
|
||||
def loadPlugins():
|
||||
for filename in os.listdir("./modules/plugins"):
|
||||
if filename.endswith(".py") or filename.endswith(".pyc"):
|
||||
if os.path.isdir("./modules/plugins/" + filename) is False:
|
||||
continue
|
||||
try:
|
||||
modules[filename] = import_module("modules.plugins.%s" % (filename))
|
||||
@@ -186,13 +188,12 @@ def loadPlugins():
|
||||
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(app):
|
||||
print "INITIALIZE CUSTOM PLUGINS"
|
||||
#@cbpi.initalizer(order=1)
|
||||
def initPlugins():
|
||||
loadCorePlugins()
|
||||
loadPlugins()
|
||||
|
||||
@cbpi.initalizer(order=2)
|
||||
def init(cbpi):
|
||||
print "INITIALIZE ADDON MODULE"
|
||||
|
||||
cbpi.app.register_blueprint(blueprint, url_prefix='/api/editor')
|
||||
|
||||
@@ -15,7 +15,9 @@ from modules.core.db import DBModel
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
logging.basicConfig(filename='./logs/app.log',level=logging.INFO)
|
||||
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'
|
||||
|
||||
@@ -45,7 +47,7 @@ class ComplexEncoder(json.JSONEncoder):
|
||||
return None
|
||||
|
||||
app.json_encoder = ComplexEncoder
|
||||
socketio = SocketIO(app, json=json)
|
||||
socketio = SocketIO(app, json=json, logging=False)
|
||||
cbpi = CraftBeerPi(app, socketio)
|
||||
|
||||
app.logger.info("##########################################")
|
||||
|
||||
@@ -7,15 +7,16 @@ 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)
|
||||
kettle = StepProperty.Kettle("Kettle")
|
||||
timer = Property.Number("Timer in Minutes", configurable=True)
|
||||
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):
|
||||
'''
|
||||
@@ -49,13 +50,14 @@ class MashStep(StepBase):
|
||||
'''
|
||||
|
||||
# Check if Target Temp is reached
|
||||
if self.get_kettle_temp(self.kettle) >= int(self.temp):
|
||||
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()
|
||||
|
||||
|
||||
@@ -65,10 +67,14 @@ class MashInStep(StepBase):
|
||||
Just put the decorator @cbpi.step on top of a method
|
||||
'''
|
||||
# Properties
|
||||
temp = Property.Number("Temperature", configurable=True)
|
||||
kettle = StepProperty.Kettle("Kettle")
|
||||
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
|
||||
@@ -87,9 +93,137 @@ class MashInStep(StepBase):
|
||||
'''
|
||||
|
||||
# Check if Target Temp is reached
|
||||
if self.get_kettle_temp(self.kettle) >= int(self.temp) and self.s is False:
|
||||
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()
|
||||
|
||||
@@ -10,21 +10,38 @@ from modules.core.props import Property
|
||||
|
||||
@cbpi.sensor
|
||||
class DummyTempSensor(SensorActive):
|
||||
temp = Property.Number("Temperature", configurable=True, default_value=5)
|
||||
|
||||
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):
|
||||
pass
|
||||
SensorActive.stop(self)
|
||||
|
||||
def execute(self):
|
||||
while self.is_running():
|
||||
|
||||
'''
|
||||
Active sensor has to handle his own loop
|
||||
:return:
|
||||
'''
|
||||
while self.is_running() is True:
|
||||
self.data_received(self.temp)
|
||||
socketio.sleep(5)
|
||||
|
||||
|
||||
self.sleep(5)
|
||||
|
||||
@classmethod
|
||||
def init_global(cls):
|
||||
'''
|
||||
Called one at the startup for all sensors
|
||||
:return:
|
||||
'''
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -6,10 +6,10 @@ from modules.core.props import Property
|
||||
@cbpi.fermentation_controller
|
||||
class Hysteresis(FermenterController):
|
||||
|
||||
heater_offset_min = Property.Number("Heater Offset min", True, 0)
|
||||
heater_offset_max = Property.Number("Heater Offset max", True, 0)
|
||||
cooler_offset_min = Property.Number("Cooler Offset min", True, 0)
|
||||
cooler_offset_max = Property.Number("Cooler Offset max", True, 0)
|
||||
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()
|
||||
@@ -23,16 +23,16 @@ class Hysteresis(FermenterController):
|
||||
target_temp = self.get_target_temp()
|
||||
temp = self.get_temp()
|
||||
|
||||
if temp + int(self.heater_offset_min) < target_temp:
|
||||
if temp + float(self.heater_offset_min) <= target_temp:
|
||||
self.heater_on(100)
|
||||
|
||||
if temp + int(self.heater_offset_max) > target_temp:
|
||||
if temp + float(self.heater_offset_max) >= target_temp:
|
||||
self.heater_off()
|
||||
|
||||
if temp > target_temp + int(self.cooler_offset_min):
|
||||
if temp >= target_temp + float(self.cooler_offset_min):
|
||||
self.cooler_on(100)
|
||||
|
||||
if temp < target_temp + int(self.cooler_offset_max):
|
||||
if temp <= target_temp + float(self.cooler_offset_max):
|
||||
self.cooler_off()
|
||||
|
||||
self.sleep(1)
|
||||
@@ -18,7 +18,7 @@ except Exception as e:
|
||||
@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])
|
||||
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)
|
||||
@@ -35,11 +35,11 @@ class GPIOSimple(ActorBase):
|
||||
@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])
|
||||
duty_cylce = Property.Number("Duty Cycle", configurable=True)
|
||||
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
|
||||
power = 100 # duty cycle
|
||||
|
||||
def init(self):
|
||||
GPIO.setup(int(self.gpio), GPIO.OUT)
|
||||
@@ -50,13 +50,18 @@ class GPIOPWM(ActorBase):
|
||||
if power is not None:
|
||||
self.power = int(power)
|
||||
|
||||
if self.duty_cylce is None:
|
||||
duty_cylce = 50
|
||||
if self.frequency is None:
|
||||
self.frequency = 0.5 # 2 sec
|
||||
|
||||
self.p = GPIO.PWM(int(self.gpio), int(self.duty_cylce))
|
||||
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)
|
||||
@@ -69,7 +74,7 @@ class GPIOPWM(ActorBase):
|
||||
@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])
|
||||
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)
|
||||
@@ -86,8 +91,17 @@ class RelayBoard(ActorBase):
|
||||
@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"
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ class Hysteresis(KettleController):
|
||||
|
||||
# Custom Properties
|
||||
|
||||
on = Property.Number("Offset On", True, 0)
|
||||
off = Property.Number("Offset Off", True, 0)
|
||||
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):
|
||||
'''
|
||||
@@ -29,11 +29,10 @@ class Hysteresis(KettleController):
|
||||
:return:
|
||||
'''
|
||||
while self.is_running():
|
||||
if self.get_temp() < self.get_target_temp() - int(self.on):
|
||||
|
||||
if self.get_temp() < self.get_target_temp() - float(self.on):
|
||||
self.heater_on(100)
|
||||
elif self.get_temp() >= self.get_target_temp() - int(self.off):
|
||||
self.heater_off()
|
||||
else:
|
||||
elif self.get_temp() >= self.get_target_temp() - float(self.off):
|
||||
self.heater_off()
|
||||
self.sleep(1)
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ def getSensors():
|
||||
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:
|
||||
@@ -66,7 +67,8 @@ class myThread (threading.Thread):
|
||||
@cbpi.sensor
|
||||
class ONE_WIRE_SENSOR(SensorPassive):
|
||||
|
||||
sensor_name = Property.Select("Sensor", getSensors())
|
||||
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):
|
||||
|
||||
@@ -86,9 +88,13 @@ class ONE_WIRE_SENSOR(SensorPassive):
|
||||
|
||||
def read(self):
|
||||
if self.get_config_parameter("unit", "C") == "C":
|
||||
self.data_received(self.t.value)
|
||||
self.data_received(round(self.t.value + self.offset_value(), 2))
|
||||
else:
|
||||
self.data_received(format(9.0 / 5.0 * self.t.value + 32, '.2f'))
|
||||
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):
|
||||
@@ -108,5 +114,5 @@ def set_temp(t):
|
||||
|
||||
@cbpi.initalizer()
|
||||
def init(cbpi):
|
||||
cbpi.app.logger.info("INITIALIZE ONE WIRE MODULE")
|
||||
|
||||
cbpi.app.register_blueprint(blueprint, url_prefix='/api/one_wire')
|
||||
+17
-11
@@ -9,16 +9,18 @@ except Exception as e:
|
||||
|
||||
class Buzzer(object):
|
||||
|
||||
sound = ["H", 0.1, "L"]
|
||||
def __init__(self, gpio):
|
||||
sound = ["H", 0.1, "L", 0.1, "H", 0.1, "L", 0.1, "H", 0.1, "L"]
|
||||
def __init__(self, gpio, beep_level):
|
||||
try:
|
||||
cbpi.app.logger.info("INIT BUZZER NOW GPIO%s" % gpio)
|
||||
self.gpio = gpio
|
||||
self.gpio = int(gpio)
|
||||
self.beep_level = beep_level
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
GPIO.setup(gpio, GPIO.OUT)
|
||||
GPIO.setup(self.gpio, GPIO.OUT)
|
||||
self.state = True
|
||||
cbpi.app.logger.info("BUZZER SETUP OK")
|
||||
except Exception as e:
|
||||
cbpi.app.logger.info("BUZZER EXCEPTION %s" % str(e))
|
||||
self.state = False
|
||||
|
||||
def beep(self):
|
||||
@@ -30,10 +32,14 @@ class Buzzer(object):
|
||||
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:
|
||||
@@ -41,11 +47,11 @@ class Buzzer(object):
|
||||
|
||||
start_new_thread(play, (self.sound,))
|
||||
|
||||
@cbpi.initalizer(order=0)
|
||||
@cbpi.initalizer(order=1)
|
||||
def init(cbpi):
|
||||
print "INIT BUZZER"
|
||||
cbpi.app.logger.info("INIT BUZZER")
|
||||
gpio = cbpi.get_config_parameter("buzzer", 16)
|
||||
cbpi.buzzer = Buzzer(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")
|
||||
|
||||
@@ -24,10 +24,9 @@ class ConfigView(BaseView):
|
||||
update_data = {"name": data["name"], "value": data["value"]}
|
||||
|
||||
if self.api.cache.get(self.cache_key) is not None:
|
||||
#self.pre_post_callback(self.api.cache.get(self.cache_key)[name])
|
||||
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._post_put_callback(self.api.cache.get(self.cache_key)[name])
|
||||
return json.dumps(self.api.cache.get(self.cache_key)[name].__dict__)
|
||||
|
||||
@route('/<id>', methods=["GET"])
|
||||
@@ -51,9 +50,8 @@ class ConfigView(BaseView):
|
||||
cls.post_init_callback(value)
|
||||
cls.api.cache[cls.cache_key][value.name] = value
|
||||
|
||||
|
||||
@cbpi.initalizer(order=1)
|
||||
@cbpi.initalizer(order=0)
|
||||
def init(cbpi):
|
||||
print "INITIALIZE CONFIG MODULE"
|
||||
|
||||
ConfigView.register(cbpi.app, route_base='/api/config')
|
||||
ConfigView.init_cache()
|
||||
|
||||
+15
-15
@@ -9,7 +9,7 @@ class BaseView(FlaskView):
|
||||
cache_key = None
|
||||
api = cbpi
|
||||
|
||||
@route('/<int:id>')
|
||||
@route('/<int:id>', methods=["GET"])
|
||||
def getOne(self, id):
|
||||
|
||||
if self.api.cache.get(self.cache_key) is not None:
|
||||
@@ -17,36 +17,36 @@ class BaseView(FlaskView):
|
||||
else:
|
||||
return json.dumps(self.model.get_one(id))
|
||||
|
||||
@route('/')
|
||||
@route('/', methods=["GET"])
|
||||
def getAll(self):
|
||||
if self.api.cache.get(self.cache_key) is not None:
|
||||
return json.dumps(self.api.cache.get(self.cache_key))
|
||||
else:
|
||||
return json.dumps(self.model.get_all())
|
||||
|
||||
def pre_post_callback(self, data):
|
||||
def _pre_post_callback(self, data):
|
||||
pass
|
||||
|
||||
|
||||
def post_post_callback(self, m):
|
||||
def _post_post_callback(self, m):
|
||||
pass
|
||||
|
||||
@route('/', methods=["POST"])
|
||||
def post(self):
|
||||
data = request.json
|
||||
self.pre_post_callback(data)
|
||||
self._pre_post_callback(data)
|
||||
m = self.model.insert(**data)
|
||||
if self.api.cache.get(self.cache_key) is not None:
|
||||
self.api.cache.get(self.cache_key)[m.id] = m
|
||||
|
||||
self.post_post_callback(m)
|
||||
self._post_post_callback(m)
|
||||
|
||||
return json.dumps(m)
|
||||
|
||||
def pre_put_callback(self, m):
|
||||
def _pre_put_callback(self, m):
|
||||
pass
|
||||
|
||||
def post_put_callback(self, m):
|
||||
def _post_put_callback(self, m):
|
||||
pass
|
||||
|
||||
|
||||
@@ -59,32 +59,32 @@ class BaseView(FlaskView):
|
||||
except:
|
||||
pass
|
||||
if self.api.cache.get(self.cache_key) is not None:
|
||||
self.pre_put_callback(self.api.cache.get(self.cache_key)[id])
|
||||
self._pre_put_callback(self.api.cache.get(self.cache_key)[id])
|
||||
self.api.cache.get(self.cache_key)[id].__dict__.update(**data)
|
||||
m = self.model.update(**self.api.cache.get(self.cache_key)[id].__dict__)
|
||||
self.post_put_callback(self.api.cache.get(self.cache_key)[id])
|
||||
self._post_put_callback(self.api.cache.get(self.cache_key)[id])
|
||||
return json.dumps(self.api.cache.get(self.cache_key)[id])
|
||||
else:
|
||||
m = self.model.update(**data)
|
||||
|
||||
self.post_put_callback(m)
|
||||
self._post_put_callback(m)
|
||||
return json.dumps(m)
|
||||
|
||||
|
||||
def pre_delete_callback(self, m):
|
||||
def _pre_delete_callback(self, m):
|
||||
pass
|
||||
|
||||
def post_delete_callback(self, id):
|
||||
def _post_delete_callback(self, id):
|
||||
pass
|
||||
|
||||
@route('/<int:id>', methods=["DELETE"])
|
||||
def delete(self, id):
|
||||
if self.api.cache.get(self.cache_key) is not None:
|
||||
self.pre_delete_callback(self.api.cache.get(self.cache_key)[id])
|
||||
self._pre_delete_callback(self.api.cache.get(self.cache_key)[id])
|
||||
del self.api.cache.get(self.cache_key)[id]
|
||||
m = self.model.delete(id)
|
||||
|
||||
def post_delete_callback(self, id):
|
||||
def _post_delete_callback(self, id):
|
||||
pass
|
||||
return ('',204)
|
||||
|
||||
|
||||
@@ -17,6 +17,12 @@ class ActorController(object):
|
||||
|
||||
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):
|
||||
|
||||
@@ -68,6 +74,14 @@ class ControllerBase(object):
|
||||
|
||||
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")
|
||||
@@ -101,6 +115,13 @@ class KettleController(ControllerBase, ActorController, SensorController):
|
||||
|
||||
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")
|
||||
@@ -118,7 +139,7 @@ class FermenterController(ControllerBase, ActorController, SensorController):
|
||||
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))
|
||||
self.actor_on(power, int(f.heater))
|
||||
|
||||
@cbpi.try_catch(None)
|
||||
def heater_off(self):
|
||||
@@ -129,13 +150,13 @@ class FermenterController(ControllerBase, ActorController, SensorController):
|
||||
@cbpi.try_catch(None)
|
||||
def cooler_on(self, power=100):
|
||||
f = self.api.cache.get("fermenter").get(self.fermenter_id)
|
||||
if f.heater is not None:
|
||||
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.heater is not None:
|
||||
if f.cooler is not None:
|
||||
self.actor_off(int(f.cooler))
|
||||
|
||||
@cbpi.try_catch(None)
|
||||
|
||||
+56
-24
@@ -1,3 +1,4 @@
|
||||
import inspect
|
||||
import pprint
|
||||
|
||||
import sqlite3
|
||||
@@ -28,6 +29,7 @@ class ActorAPI(object):
|
||||
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()
|
||||
|
||||
for key in self.cache.get("actors"):
|
||||
@@ -38,7 +40,6 @@ class ActorAPI(object):
|
||||
value = self.cache.get("actors").get(int(id))
|
||||
cfg = value.config.copy()
|
||||
cfg.update(dict(api=self, id=id, name=value.name))
|
||||
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()
|
||||
@@ -73,12 +74,8 @@ class ActorAPI(object):
|
||||
|
||||
if actor.state == 0:
|
||||
return
|
||||
|
||||
|
||||
actor.instance.off()
|
||||
actor.state = 0
|
||||
|
||||
|
||||
self.emit("SWITCH_ACTOR", actor)
|
||||
|
||||
class SensorAPI(object):
|
||||
@@ -161,9 +158,8 @@ class SensorAPI(object):
|
||||
def log_action(self, text):
|
||||
filename = "./logs/action.log"
|
||||
formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime())
|
||||
|
||||
|
||||
with open(filename, "a") as file:
|
||||
text = text.encode("utf-8")
|
||||
file.write("%s,%s\n" % (formatted_time, text))
|
||||
|
||||
def shutdown_sensor(self, id):
|
||||
@@ -173,7 +169,7 @@ class SensorAPI(object):
|
||||
def get_sensor_value(self, id):
|
||||
try:
|
||||
id = int(id)
|
||||
return int(self.cache.get("sensors")[id].instance.last_value)
|
||||
return float(self.cache.get("sensors")[id].instance.last_value)
|
||||
except Exception as e:
|
||||
|
||||
return None
|
||||
@@ -226,12 +222,9 @@ class CraftBeerPi(ActorAPI, SensorAPI):
|
||||
def notify(self, headline, message, type="success", timeout=5000):
|
||||
self.beep()
|
||||
msg = {"id": str(uuid.uuid1()), "type": type, "headline": headline, "message": message, "timeout": timeout}
|
||||
if timeout is None:
|
||||
self.cache["messages"].append(msg)
|
||||
self.emit("NOTIFY", msg)
|
||||
self.emit_message(msg)
|
||||
|
||||
def beep(self):
|
||||
|
||||
if self.buzzer is not None:
|
||||
self.buzzer.beep()
|
||||
|
||||
@@ -248,6 +241,15 @@ class CraftBeerPi(ActorAPI, SensorAPI):
|
||||
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():
|
||||
@@ -264,28 +266,56 @@ class CraftBeerPi(ActorAPI, SensorAPI):
|
||||
# helper method for parsing props
|
||||
def __parseProps(self, key, cls):
|
||||
name = cls.__name__
|
||||
self.cache[key][name] = {"name": name, "class": cls, "properties": []}
|
||||
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})
|
||||
{"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})
|
||||
{"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})
|
||||
{"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)
|
||||
|
||||
@@ -300,6 +330,8 @@ class CraftBeerPi(ActorAPI, SensorAPI):
|
||||
|
||||
def get_fermentation_controller(self, name):
|
||||
return self.cache["fermentation_controller_types"].get(name)
|
||||
|
||||
|
||||
# Step action
|
||||
def action(self,label):
|
||||
def real_decorator(func):
|
||||
@@ -320,22 +352,22 @@ class CraftBeerPi(ActorAPI, SensorAPI):
|
||||
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})
|
||||
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})
|
||||
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", "options": t.options})
|
||||
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})
|
||||
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})
|
||||
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})
|
||||
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"):
|
||||
@@ -420,7 +452,7 @@ class CraftBeerPi(ActorAPI, SensorAPI):
|
||||
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("-> %s " % i.get("function").__name__)
|
||||
self.app.logger.info("INITIALIZER - METHOD %s PAHT %s: " % (i.get("function").__name__, str(inspect.getmodule(i.get("function")).__file__) ))
|
||||
i.get("function")(self)
|
||||
|
||||
|
||||
@@ -451,7 +483,7 @@ class CraftBeerPi(ActorAPI, SensorAPI):
|
||||
def job(interval, method):
|
||||
while True:
|
||||
try:
|
||||
method()
|
||||
method(self)
|
||||
except Exception as e:
|
||||
self.app.logger.error("Exception" + method.__name__ + ": " + str(e))
|
||||
self.socketio.sleep(interval)
|
||||
|
||||
+4
-2
@@ -42,13 +42,15 @@ class DBModel(object):
|
||||
def get_all(cls):
|
||||
cur = get_db().cursor()
|
||||
if cls.__order_by__ is not None:
|
||||
cur.execute("SELECT * FROM %s ORDER BY '%s'" % (cls.__table_name__,cls.__order_by__))
|
||||
|
||||
cur.execute("SELECT * FROM %s ORDER BY %s.'%s'" % (cls.__table_name__,cls.__table_name__,cls.__order_by__))
|
||||
else:
|
||||
cur.execute("SELECT * FROM %s" % cls.__table_name__)
|
||||
|
||||
if cls.__as_array__ is True:
|
||||
result = []
|
||||
for r in cur.fetchall():
|
||||
|
||||
result.append( cls(r))
|
||||
else:
|
||||
result = {}
|
||||
@@ -104,7 +106,7 @@ class DBModel(object):
|
||||
else:
|
||||
data = data + (kwargs.get(f),)
|
||||
|
||||
print query, data
|
||||
|
||||
cur.execute(query, data)
|
||||
get_db().commit()
|
||||
i = cur.lastrowid
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import sqlite3
|
||||
import os
|
||||
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"])
|
||||
return
|
||||
try:
|
||||
with sqlite3.connect("craftbeerpi.db") as conn:
|
||||
with open('./update/%s' % data["file"], 'r') as f:
|
||||
d = f.read()
|
||||
sqlCommands = d.split(";")
|
||||
cur = conn.cursor()
|
||||
for s in sqlCommands:
|
||||
cur.execute(s)
|
||||
cur.execute("INSERT INTO schema_info (version,filename) values (?,?)", (data["version"], data["file"]))
|
||||
conn.commit()
|
||||
|
||||
except sqlite3.OperationalError as err:
|
||||
print "EXCEPT"
|
||||
print err
|
||||
|
||||
@cbpi.initalizer(order=-9999)
|
||||
def init(app=None):
|
||||
|
||||
with cbpi.app.app_context():
|
||||
conn = get_db()
|
||||
cur = conn.cursor()
|
||||
current_version = None
|
||||
try:
|
||||
cur.execute("SELECT max(version) as m FROM schema_info")
|
||||
m = cur.fetchone()
|
||||
current_version = m["m"]
|
||||
except:
|
||||
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)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -5,16 +5,19 @@ class Base(object):
|
||||
|
||||
@classmethod
|
||||
def init_global(cls):
|
||||
print "GLOBAL ACTOR INIT"
|
||||
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):
|
||||
print "INIT BASE"
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
print "STOP HARDWARE"
|
||||
pass
|
||||
|
||||
def update(self, **kwds):
|
||||
pass
|
||||
@@ -67,15 +70,16 @@ class SensorActive(SensorBase):
|
||||
__running = False
|
||||
|
||||
def is_running(self):
|
||||
|
||||
return self.__running
|
||||
|
||||
def init(self):
|
||||
self.__running = True
|
||||
print "INIT ACTIVE SENSOR"
|
||||
|
||||
def stop(self):
|
||||
self.__running = False
|
||||
|
||||
|
||||
def execute(self):
|
||||
pass
|
||||
|
||||
@@ -95,10 +99,10 @@ class ActorBase(Base):
|
||||
return 1
|
||||
|
||||
def set_power(self, power):
|
||||
print "SET POWER TO %s" % power
|
||||
pass
|
||||
|
||||
def on(self, power=0):
|
||||
print "ON"
|
||||
pass
|
||||
|
||||
def off(self):
|
||||
print "OFF"
|
||||
pass
|
||||
|
||||
+36
-6
@@ -3,36 +3,66 @@ class PropertyType(object):
|
||||
|
||||
class Property(object):
|
||||
class Select(PropertyType):
|
||||
def __init__(self, label, options):
|
||||
def __init__(self, label, options, description=""):
|
||||
PropertyType.__init__(self)
|
||||
self.label = label
|
||||
self.options = options
|
||||
self.description = description
|
||||
|
||||
class Number(PropertyType):
|
||||
def __init__(self, label, configurable=False, default_value=0, unit=""):
|
||||
def __init__(self, label, configurable=False, default_value=None, unit="", description=""):
|
||||
PropertyType.__init__(self)
|
||||
self.label = label
|
||||
self.configurable = configurable
|
||||
self.default_value = default_value
|
||||
self.description = description
|
||||
|
||||
class Text(PropertyType):
|
||||
def __init__(self, label, configurable=False, default_value=""):
|
||||
def __init__(self, label, configurable=False, default_value="", description=""):
|
||||
PropertyType.__init__(self)
|
||||
self.label = label
|
||||
self.configurable = configurable
|
||||
self.default_value = default_value
|
||||
self.description = description
|
||||
|
||||
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
|
||||
|
||||
|
||||
class StepProperty(Property):
|
||||
class Actor(PropertyType):
|
||||
def __init__(self, label):
|
||||
def __init__(self, label, description=""):
|
||||
PropertyType.__init__(self)
|
||||
self.label = label
|
||||
self.configurable = True
|
||||
self.description = description
|
||||
class Sensor(PropertyType):
|
||||
def __init__(self, label):
|
||||
def __init__(self, label, description=""):
|
||||
PropertyType.__init__(self)
|
||||
self.label = label
|
||||
self.configurable = True
|
||||
self.description = description
|
||||
class Kettle(PropertyType):
|
||||
def __init__(self, label):
|
||||
def __init__(self, label, description=""):
|
||||
PropertyType.__init__(self)
|
||||
self.label = label
|
||||
self.configurable = True
|
||||
self.description = description
|
||||
|
||||
+19
-5
@@ -17,6 +17,10 @@ class ActorAPI(NotificationAPI):
|
||||
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)
|
||||
@@ -40,7 +44,7 @@ class KettleAPI(NotificationAPI):
|
||||
return self.api.cache.get("kettle").get(id).target_temp
|
||||
|
||||
def set_target_temp(self, temp, id=None):
|
||||
temp = int(temp)
|
||||
temp = float(temp)
|
||||
|
||||
try:
|
||||
if id is None:
|
||||
@@ -55,6 +59,7 @@ 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
|
||||
@@ -70,6 +75,12 @@ class Timer(object):
|
||||
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
|
||||
@@ -89,13 +100,13 @@ class StepBase(Timer, ActorAPI, SensorAPI, KettleAPI):
|
||||
self.n = True
|
||||
|
||||
def init(self):
|
||||
print "INIT STEP"
|
||||
pass
|
||||
|
||||
def finish(self):
|
||||
print "FINSIH STEP"
|
||||
pass
|
||||
|
||||
def reset(self):
|
||||
print "REST STEP"
|
||||
pass
|
||||
|
||||
def execute(self):
|
||||
print "-------------"
|
||||
@@ -105,11 +116,14 @@ class StepBase(Timer, ActorAPI, SensorAPI, KettleAPI):
|
||||
|
||||
|
||||
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
|
||||
|
||||
@@ -63,19 +63,21 @@ class FermenterView(BaseView):
|
||||
model = Fermenter
|
||||
cache_key = "fermenter"
|
||||
|
||||
def post_post_callback(self, m):
|
||||
|
||||
def _post_post_callback(self, m):
|
||||
m.state = False
|
||||
m.steps = []
|
||||
|
||||
def pre_put_callback(self, m):
|
||||
def _pre_put_callback(self, m):
|
||||
m.state = False
|
||||
try:
|
||||
m.instance.stop()
|
||||
except:
|
||||
pass
|
||||
|
||||
def post_put_callback(self, m):
|
||||
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):
|
||||
@@ -221,6 +223,8 @@ class FermenterView(BaseView):
|
||||
|
||||
def run(instance):
|
||||
instance.run()
|
||||
fermenter.state = not fermenter.state
|
||||
cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key).get(id))
|
||||
|
||||
t = cbpi.socketio.start_background_task(target=run, instance=instance)
|
||||
fermenter.state = not fermenter.state
|
||||
@@ -282,7 +286,7 @@ class FermenterView(BaseView):
|
||||
|
||||
|
||||
@cbpi.backgroundtask(key="read_target_temps_fermenter", interval=5)
|
||||
def read_target_temps():
|
||||
def read_target_temps(api):
|
||||
"""
|
||||
background process that reads all passive sensors in interval of 1 second
|
||||
:return: None
|
||||
@@ -295,7 +299,7 @@ def read_target_temps():
|
||||
instance = FermenterView()
|
||||
|
||||
@cbpi.backgroundtask(key="fermentation_task", interval=1)
|
||||
def execute_fermentation_step():
|
||||
def execute_fermentation_step(api):
|
||||
with cbpi.app.app_context():
|
||||
instance.check_step()
|
||||
|
||||
@@ -310,6 +314,6 @@ def init_active_steps():
|
||||
|
||||
@cbpi.initalizer(order=1)
|
||||
def init(cbpi):
|
||||
print "INITIALIZE CONFIG MODULE"
|
||||
|
||||
FermenterView.register(cbpi.app, route_base='/api/fermenter')
|
||||
FermenterView.init_cache()
|
||||
|
||||
@@ -15,7 +15,7 @@ class Kettle2View(BaseView):
|
||||
cache_key = "kettle"
|
||||
|
||||
@classmethod
|
||||
def pre_post_callback(self, data):
|
||||
def _pre_post_callback(self, data):
|
||||
data["target_temp"] = 0
|
||||
|
||||
@classmethod
|
||||
@@ -23,16 +23,16 @@ class Kettle2View(BaseView):
|
||||
obj.state = False
|
||||
|
||||
|
||||
def post_post_callback(self, m):
|
||||
def _post_post_callback(self, m):
|
||||
m.state = False
|
||||
|
||||
def pre_put_callback(self, m):
|
||||
def _pre_put_callback(self, m):
|
||||
try:
|
||||
m.instance.stop()
|
||||
except:
|
||||
pass
|
||||
|
||||
def post_put_callback(self, m):
|
||||
def _post_put_callback(self, m):
|
||||
m.state = False
|
||||
|
||||
@route('/<int:id>/targettemp/<temp>', methods=['POST'])
|
||||
@@ -76,11 +76,11 @@ def set_target_temp(id, temp):
|
||||
:param temp: target temp to set
|
||||
:return: None
|
||||
'''
|
||||
print "GOT EVENT %s %s" % (id, temp)
|
||||
|
||||
Kettle2View().postTargetTemp(id,temp)
|
||||
|
||||
@cbpi.backgroundtask(key="read_target_temps", interval=5)
|
||||
def read_target_temps():
|
||||
def read_target_temps(api):
|
||||
"""
|
||||
background process that reads all passive sensors in interval of 1 second
|
||||
:return: None
|
||||
|
||||
@@ -65,6 +65,8 @@ class LogView(FlaskView):
|
||||
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):
|
||||
@@ -76,13 +78,12 @@ class LogView(FlaskView):
|
||||
|
||||
if t == "k":
|
||||
kettle = cbpi.cache.get("kettle").get(id)
|
||||
result.append({"name": "Temp", "data": self.read_log_as_json("sensor", kettle.sensor)})
|
||||
result.append({"name": "Target Temp", "data": self.read_log_as_json("kettle", kettle.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.append({"name": "Temp", "data": self.read_log_as_json("sensor", fermenter.sensor)})
|
||||
result.append({"name": "Target Temp", "data": self.read_log_as_json("fermenter", fermenter.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>')
|
||||
@@ -105,5 +106,4 @@ def init(app):
|
||||
:param app: the flask app
|
||||
:return: None
|
||||
"""
|
||||
print "INITIALIZE LOG MODULE"
|
||||
LogView.register(cbpi.app, route_base='/api/logs')
|
||||
|
||||
@@ -32,10 +32,9 @@ def messageEvent(message, **kwargs):
|
||||
:param kwargs: other parameter
|
||||
:return: None
|
||||
"""
|
||||
|
||||
msg = {"id": len(cbpi.cache["messages"]), "type": "info", "message": message, "read": False}
|
||||
cbpi.cache["messages"].append(msg)
|
||||
cbpi.emit('MESSAGE', msg,)
|
||||
if message["timeout"] is None:
|
||||
cbpi.cache["messages"].append(message)
|
||||
cbpi.emit("NOTIFY", message)
|
||||
|
||||
@cbpi.initalizer(order=2)
|
||||
def init(cbpi):
|
||||
@@ -44,9 +43,8 @@ def init(cbpi):
|
||||
:param app: the flask app
|
||||
:return: None
|
||||
"""
|
||||
print "INITIALIZE MESSAGE MODULE"
|
||||
|
||||
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)
|
||||
if cbpi.get_config_parameter("donation_notification", "YES") == "YES":
|
||||
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')
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import beerxml
|
||||
import kbh
|
||||
import restapi
|
||||
@@ -0,0 +1,152 @@
|
||||
from flask import json, request
|
||||
from flask_classy import FlaskView, route
|
||||
from git import Repo, Git
|
||||
import sqlite3
|
||||
from modules.app_config import cbpi
|
||||
from werkzeug.utils import secure_filename
|
||||
import pprint
|
||||
import time
|
||||
import os
|
||||
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):
|
||||
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")
|
||||
return ('', 404)
|
||||
result = []
|
||||
|
||||
e = xml.etree.ElementTree.parse(self.BEER_XML_FILE).getroot()
|
||||
result = []
|
||||
for idx, val in enumerate(e.findall('RECIPE')):
|
||||
result.append({"id": idx+1, "name": val.find("NAME").text})
|
||||
return json.dumps(result)
|
||||
|
||||
def allowed_file(self, filename):
|
||||
return '.' in filename and filename.rsplit('.', 1)[1] in set(['xml'])
|
||||
|
||||
@route('/upload', methods=['POST'])
|
||||
def upload_file(self):
|
||||
try:
|
||||
if request.method == 'POST':
|
||||
file = request.files['file']
|
||||
if file and self.allowed_file(file.filename):
|
||||
file.save(os.path.join(self.api.app.config['UPLOAD_FOLDER'], "beer.xml"))
|
||||
self.api.notify(headline="Upload Successful", message="The Beer XML file was uploaded succesfully")
|
||||
return ('', 204)
|
||||
return ('', 404)
|
||||
except Exception as e:
|
||||
self.api.notify(headline="Upload Failed", message="Failed to upload Beer xml", type="danger")
|
||||
return ('', 500)
|
||||
|
||||
@route('/<int:id>', methods=['POST'])
|
||||
def load(self, id):
|
||||
|
||||
|
||||
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)
|
||||
mashstep_type = cbpi.get_config_parameter("step_mash", "MashStep")
|
||||
mash_kettle = cbpi.get_config_parameter("step_mash_kettle", None)
|
||||
|
||||
boilstep_type = cbpi.get_config_parameter("step_boil", "BoilStep")
|
||||
boil_kettle = cbpi.get_config_parameter("step_boil_kettle", None)
|
||||
boil_temp = 100 if cbpi.get_config_parameter("unit", "C") == "C" else 212
|
||||
|
||||
# READ KBH DATABASE
|
||||
Step.delete_all()
|
||||
StepView().reset()
|
||||
|
||||
try:
|
||||
|
||||
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 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:
|
||||
self.api.notify(headline="Failed to load Recipe", message=e.message, type="danger")
|
||||
return ('', 500)
|
||||
|
||||
return ('', 204)
|
||||
|
||||
def getRecipeName(self, id):
|
||||
e = xml.etree.ElementTree.parse(self.BEER_XML_FILE).getroot()
|
||||
return e.find('./RECIPE[%s]/NAME' % (str(id))).text
|
||||
|
||||
def getBoilTime(self, id):
|
||||
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.initalizer()
|
||||
def init(cbpi):
|
||||
|
||||
BeerXMLImport.api = cbpi
|
||||
BeerXMLImport.register(cbpi.app, route_base='/api/beerxml')
|
||||
@@ -0,0 +1,109 @@
|
||||
from flask import json, request
|
||||
from flask_classy import FlaskView, route
|
||||
from git import Repo, Git
|
||||
import sqlite3
|
||||
from modules.app_config import cbpi
|
||||
from werkzeug.utils import secure_filename
|
||||
import pprint
|
||||
import time
|
||||
import os
|
||||
from modules.steps import Step, StepView
|
||||
|
||||
|
||||
class KBH(FlaskView):
|
||||
|
||||
@route('/', methods=['GET'])
|
||||
def get(self):
|
||||
conn = None
|
||||
try:
|
||||
if not os.path.exists(self.api.app.config['UPLOAD_FOLDER'] + '/kbh.db'):
|
||||
self.api.notify(headline="File Not Found", message="Please upload a Kleiner Brauhelfer Database", type="danger")
|
||||
return ('', 404)
|
||||
|
||||
conn = sqlite3.connect(self.api.app.config['UPLOAD_FOLDER'] + '/kbh.db')
|
||||
c = conn.cursor()
|
||||
c.execute('SELECT ID, Sudname, BierWurdeGebraut FROM Sud')
|
||||
data = c.fetchall()
|
||||
result = []
|
||||
for row in data:
|
||||
result.append({"id": row[0], "name": row[1], "brewed": row[2]})
|
||||
return json.dumps(result)
|
||||
except Exception as e:
|
||||
print e
|
||||
self.api.notify(headline="Failed to load KHB database", message="ERROR", type="danger")
|
||||
return ('', 500)
|
||||
finally:
|
||||
if conn:
|
||||
conn.close()
|
||||
|
||||
def allowed_file(self, filename):
|
||||
return '.' in filename and filename.rsplit('.', 1)[1] in set(['sqlite'])
|
||||
|
||||
@route('/upload', methods=['POST'])
|
||||
def upload_file(self):
|
||||
try:
|
||||
if request.method == 'POST':
|
||||
file = request.files['file']
|
||||
if file and self.allowed_file(file.filename):
|
||||
filename = secure_filename(file.filename)
|
||||
file.save(os.path.join(self.api.app.config['UPLOAD_FOLDER'], "kbh.db"))
|
||||
self.api.notify(headline="Upload Successful", message="The Kleiner Brauhelfer Database was uploaded succesfully")
|
||||
return ('', 204)
|
||||
return ('', 404)
|
||||
except Exception as e:
|
||||
self.api.notify(headline="Upload Failed", message="Failed to upload Kleiner Brauhelfer", type="danger")
|
||||
|
||||
return ('', 500)
|
||||
|
||||
@route('/<int:id>', methods=['POST'])
|
||||
def load(self, id):
|
||||
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")
|
||||
boilstep_type = cbpi.get_config_parameter("step_boil", "BoilStep")
|
||||
mash_kettle = cbpi.get_config_parameter("step_mash_kettle", None)
|
||||
boil_kettle = cbpi.get_config_parameter("step_boil_kettle", None)
|
||||
boil_temp = 100 if cbpi.get_config_parameter("unit", "C") == "C" else 212
|
||||
|
||||
# READ KBH DATABASE
|
||||
Step.delete_all()
|
||||
StepView().reset()
|
||||
conn = None
|
||||
try:
|
||||
conn = sqlite3.connect(self.api.app.config['UPLOAD_FOLDER'] + '/kbh.db')
|
||||
c = conn.cursor()
|
||||
c.execute('SELECT EinmaischenTemp, Sudname FROM Sud WHERE ID = ?', (id,))
|
||||
row = c.fetchone()
|
||||
name = row[1]
|
||||
|
||||
self.api.set_config_parameter("brew_name", name)
|
||||
Step.insert(**{"name": "MashIn", "type": mashinstep_type, "config": {"kettle": mash_kettle, "temp": row[0]}})
|
||||
### add rest step
|
||||
for row in c.execute('SELECT * FROM Rasten WHERE SudID = ?', (id,)):
|
||||
Step.insert(**{"name": row[5], "type": mashstep_type, "config": {"kettle": mash_kettle, "temp": row[3], "timer": row[4]}})
|
||||
Step.insert(**{"name": "Chil", "type": chilstep_type, "config": {"timer": 15}})
|
||||
## Add cooking step
|
||||
c.execute('SELECT max(Zeit) FROM Hopfengaben WHERE SudID = ?', (id,))
|
||||
row = c.fetchone()
|
||||
Step.insert(**{"name": "Boil", "type": boilstep_type, "config": {"kettle": boil_kettle, "temp": boil_temp, "timer": row[0]}})
|
||||
## Add Whirlpool step
|
||||
Step.insert(**{"name": "Whirlpool", "type": chilstep_type, "config": {"timer": 15}})
|
||||
|
||||
#setBrewName(name)
|
||||
self.api.emit("UPDATE_ALL_STEPS", Step.get_all())
|
||||
self.api.notify(headline="Recipe %s loaded successfully" % name, message="")
|
||||
except Exception as e:
|
||||
self.api.notify(headline="Failed to load Recipe", message=e.message, type="danger")
|
||||
return ('', 500)
|
||||
finally:
|
||||
if conn:
|
||||
conn.close()
|
||||
return ('', 204)
|
||||
|
||||
|
||||
|
||||
@cbpi.initalizer()
|
||||
def init(cbpi):
|
||||
|
||||
KBH.api = cbpi
|
||||
KBH.register(cbpi.app, route_base='/api/kbh')
|
||||
@@ -0,0 +1,62 @@
|
||||
from flask import json, request
|
||||
from flask_classy import FlaskView, route
|
||||
from git import Repo, Git
|
||||
import sqlite3
|
||||
from modules.app_config import cbpi
|
||||
from werkzeug.utils import secure_filename
|
||||
import pprint
|
||||
import time
|
||||
import os
|
||||
from modules.steps import Step,StepView
|
||||
import xml.etree.ElementTree
|
||||
|
||||
|
||||
class RESTImport(FlaskView):
|
||||
|
||||
|
||||
@route('/', methods=['POST'])
|
||||
def load(self):
|
||||
|
||||
try:
|
||||
data = request.json
|
||||
|
||||
name = data.get("name", "No Name")
|
||||
|
||||
self.api.set_config_parameter("brew_name", name)
|
||||
chilstep_type = cbpi.get_config_parameter("step_chil", "ChilStep")
|
||||
mashstep_type = cbpi.get_config_parameter("step_mash", "MashStep")
|
||||
mash_kettle = cbpi.get_config_parameter("step_mash_kettle", None)
|
||||
|
||||
boilstep_type = cbpi.get_config_parameter("step_boil", "BoilStep")
|
||||
boil_kettle = cbpi.get_config_parameter("step_boil_kettle", None)
|
||||
boil_temp = 100 if cbpi.get_config_parameter("unit", "C") == "C" else 212
|
||||
|
||||
# READ KBH DATABASE
|
||||
Step.delete_all()
|
||||
StepView().reset()
|
||||
|
||||
|
||||
for step in data.get("steps"):
|
||||
if step.get("type", None) == "MASH":
|
||||
Step.insert(**{"name": step.get("name","Mash Step"), "type": mashstep_type, "config": {"kettle": mash_kettle, "temp": step.get("temp",0), "timer": step.get("timer",0)}})
|
||||
elif step.get("type", None) == "CHIL":
|
||||
Step.insert(**{"name": step.get("name","Chil"), "type": chilstep_type, "config": {"timer": step.get("timer")}})
|
||||
elif step.get("type", None) == "BOIL":
|
||||
Step.insert(**{"name": step.get("name", "Boil"), "type": boilstep_type, "config": {"kettle": boil_kettle, "timer": step.get("timer"), "temp": boil_temp}})
|
||||
else:
|
||||
pass
|
||||
|
||||
self.api.emit("UPDATE_ALL_STEPS", Step.get_all())
|
||||
self.api.notify(headline="Recipe %s loaded successfully" % name, message="")
|
||||
except Exception as e:
|
||||
self.api.notify(headline="Failed to load Recipe", type="danger", message=str(e))
|
||||
m = str(e.message)
|
||||
return (str(e), 500)
|
||||
|
||||
return ('', 204)
|
||||
|
||||
|
||||
@cbpi.initalizer()
|
||||
def init(cbpi):
|
||||
RESTImport.api = cbpi
|
||||
RESTImport.register(cbpi.app, route_base='/api/recipe/import/v1')
|
||||
@@ -12,27 +12,32 @@ class SensorView(BaseView):
|
||||
model = Sensor
|
||||
cache_key = "sensors"
|
||||
|
||||
@route('<int:id>/action/<method>', methods=["POST"])
|
||||
def action(self, id, method):
|
||||
|
||||
def post_post_callback(self, m):
|
||||
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):
|
||||
def _post_put_callback(self, m):
|
||||
cbpi.stop_sensor(m.id)
|
||||
cbpi.init_sensor(m.id)
|
||||
|
||||
def pre_delete_callback(self, m):
|
||||
def _pre_delete_callback(self, m):
|
||||
cbpi.stop_sensor(m.id)
|
||||
|
||||
@cbpi.initalizer(order=1000)
|
||||
def init(cbpi):
|
||||
print "INITIALIZE SENSOR MODULE"
|
||||
|
||||
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():
|
||||
def read_passive_sensor(api):
|
||||
"""
|
||||
background process that reads all passive sensors in interval of 1 second
|
||||
:return: None
|
||||
|
||||
@@ -16,7 +16,7 @@ def getserial():
|
||||
|
||||
@cbpi.initalizer(order=9999)
|
||||
def sendStats(cbpi):
|
||||
print "INITIALIZE STATS"
|
||||
|
||||
try:
|
||||
serial = getserial()
|
||||
|
||||
|
||||
+24
-15
@@ -1,5 +1,5 @@
|
||||
import time
|
||||
from flask import json
|
||||
from flask import json, request
|
||||
from flask_classy import route
|
||||
|
||||
from modules import DBModel, cbpi, get_db
|
||||
@@ -21,9 +21,9 @@ class Step(DBModel):
|
||||
return r.get("order")
|
||||
|
||||
@classmethod
|
||||
def get_by_state(cls, state):
|
||||
def get_by_state(cls, state, order=True):
|
||||
cur = get_db().cursor()
|
||||
cur.execute("SELECT * FROM %s WHERE state = ?" % cls.__table_name__, state)
|
||||
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)
|
||||
@@ -54,13 +54,29 @@ class Step(DBModel):
|
||||
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):
|
||||
model = Step
|
||||
def pre_post_callback(self, data):
|
||||
def _pre_post_callback(self, data):
|
||||
order = self.model.get_max_order()
|
||||
data["order"] = 1 if order is None else order + 1
|
||||
data["state"] = "I"
|
||||
|
||||
@route('/sort', methods=["POST"])
|
||||
def sort_steps(self):
|
||||
Step.sort(request.json)
|
||||
cbpi.emit("UPDATE_ALL_STEPS", self.model.get_all())
|
||||
return ('', 204)
|
||||
|
||||
@route('/', methods=["DELETE"])
|
||||
def deleteAll(self):
|
||||
self.model.delete_all()
|
||||
@@ -69,16 +85,12 @@ class StepView(BaseView):
|
||||
|
||||
@route('/action/<method>', methods=["POST"])
|
||||
def action(self, method):
|
||||
|
||||
cbpi.cache["active_step"].__getattribute__(method)()
|
||||
|
||||
return ('', 204)
|
||||
|
||||
@route('/reset', methods=["POST"])
|
||||
def reset(self):
|
||||
|
||||
self.model.reset_all_steps()
|
||||
#db.session.commit()
|
||||
self.stop_step()
|
||||
cbpi.emit("UPDATE_ALL_STEPS", self.model.get_all())
|
||||
return ('', 204)
|
||||
@@ -115,7 +127,6 @@ class StepView(BaseView):
|
||||
return ('', 204)
|
||||
|
||||
def init_step(self, step):
|
||||
|
||||
cbpi.log_action("Start Step %s" % step.name)
|
||||
type_cfg = cbpi.cache.get("step_types").get(step.type)
|
||||
if type_cfg is None:
|
||||
@@ -128,7 +139,6 @@ class StepView(BaseView):
|
||||
cfg.update(dict(name=step.name, api=cbpi, id=step.id, timer_end=None, managed_fields=get_manged_fields_as_array(type_cfg)))
|
||||
instance = type_cfg.get("class")(**cfg)
|
||||
instance.init()
|
||||
|
||||
# set step instance to ache
|
||||
cbpi.cache["active_step"] = instance
|
||||
|
||||
@@ -185,15 +195,16 @@ def init_after_startup():
|
||||
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, timer_end=None, managed_fields=get_manged_fields_as_array(type_cfg)))
|
||||
cfg.update(dict(api=cbpi, id=step.id, managed_fields=get_manged_fields_as_array(type_cfg)))
|
||||
instance = type_cfg.get("class")(**cfg)
|
||||
instance.init()
|
||||
cbpi.cache["active_step"] = instance
|
||||
|
||||
@cbpi.initalizer(order=2000)
|
||||
def init(cbpi):
|
||||
print "INITIALIZE STEPS MODULE"
|
||||
|
||||
StepView.register(cbpi.app, route_base='/api/step')
|
||||
|
||||
def get_all():
|
||||
@@ -205,7 +216,7 @@ def init(cbpi):
|
||||
cbpi.add_cache_callback("steps", get_all)
|
||||
|
||||
@cbpi.backgroundtask(key="step_task", interval=0.1)
|
||||
def execute_step():
|
||||
def execute_step(api):
|
||||
'''
|
||||
Background job which executes the step
|
||||
:return:
|
||||
@@ -214,9 +225,7 @@ def execute_step():
|
||||
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)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from flask import json
|
||||
import yaml
|
||||
from flask import json, url_for, Response
|
||||
from flask_classy import FlaskView, route
|
||||
from git import Repo, Git
|
||||
|
||||
@@ -101,7 +102,44 @@ class SystemView(FlaskView):
|
||||
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):
|
||||
print "INITIALIZE SYSTEM MODULE"
|
||||
|
||||
SystemView.api = cbpi
|
||||
SystemView.register(cbpi.app, route_base='/api/system')
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
from flask import Blueprint, render_template, request
|
||||
from modules import cbpi
|
||||
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):
|
||||
print "INITIALIZE UI"
|
||||
cbpi.app.register_blueprint(react, url_prefix='/ui')
|
||||
|
||||
|
||||
@react.route('/')
|
||||
|
||||
|
||||
@react.route('/', methods=["GET"])
|
||||
def index():
|
||||
return react.send_static_file("index.html")
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
|
||||
.container {
|
||||
padding-right: 5px;
|
||||
padding-left: 5px;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.container-fluid {
|
||||
padding-right: 5px;
|
||||
padding-left: 5px;
|
||||
|
||||
}
|
||||
|
||||
.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 {
|
||||
position: relative;
|
||||
min-height: 1px;
|
||||
padding-right: 8px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.panel-heading {
|
||||
padding: 5px 5px;
|
||||
}
|
||||
|
||||
.panel-footer {
|
||||
padding: 2px 2px;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
+49
-52
File diff suppressed because one or more lines are too long
@@ -10,61 +10,12 @@
|
||||
<link rel="stylesheet" href="static/bootstrap.dark.css">
|
||||
|
||||
|
||||
<style>
|
||||
|
||||
|
||||
.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 {
|
||||
position: relative;
|
||||
min-height: 1px;
|
||||
padding-right: 8px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.panel-heading {
|
||||
padding: 5px 5px;
|
||||
|
||||
}
|
||||
|
||||
.panel-footer {
|
||||
padding: 2px 2px;
|
||||
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding: 5px;
|
||||
|
||||
|
||||
}
|
||||
.row {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex !important;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.row > [class*='col-'] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
<title>CraftBeerPi 3.0</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<div id="root" ></div>
|
||||
<script src="static/bundle.js" type="text/javascript"></script>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start`.
|
||||
To create a production bundle, use `npm run build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Executable → Regular
+4
-5
@@ -1,4 +1,4 @@
|
||||
Flask==0.11.1
|
||||
Flask==0.12.4
|
||||
Flask-SocketIO==2.6.2
|
||||
eventlet==0.19.0
|
||||
greenlet==0.4.10
|
||||
@@ -6,10 +6,9 @@ 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
|
||||
httplib2==0.19.0
|
||||
flask-classy==0.6.10
|
||||
GitPython==2.1.3
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from modules import socketio, app
|
||||
from modules import socketio, app, cbpi
|
||||
|
||||
socketio.run(app, host='0.0.0.0')
|
||||
try:
|
||||
port = int(cbpi.get_config_parameter('port', '5000'))
|
||||
except ValueError:
|
||||
port = 5000
|
||||
|
||||
socketio.run(app, host='0.0.0.0', port=port)
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
CREATE TABLE IF NOT EXISTS schema_info
|
||||
(
|
||||
id INTEGER PRIMARY KEY NOT NULL,
|
||||
version INTEGER,
|
||||
filename VARCHAR(80),
|
||||
creation_data DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
|
||||
INSERT OR IGNORE INTO config VALUES ('step_mashin', 'MashStep', 'step', 'Default MashIn Step type for import', NULL );
|
||||
INSERT OR IGNORE INTO config VALUES ('step_mash', 'MashStep', 'step', 'Default Mash Step type for import', NULL);
|
||||
INSERT OR IGNORE INTO config VALUES ('step_boil', 'BoilStep', 'step', 'Default Boil Step type for import', NULL);
|
||||
INSERT OR IGNORE INTO config VALUES ('step_chil', 'ChilStep', 'step', 'Default Chil Step type for import', NULL);
|
||||
INSERT OR IGNORE INTO config VALUES ('step_mash_kettle', 1, 'kettle', 'Default Mash Tun', NULL);
|
||||
INSERT OR IGNORE INTO config VALUES ('step_boil_kettle', 1, 'kettle', 'Default Boil Tun ', NULL);
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
UPDATE config set `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]' WHERE name ='buzzer';
|
||||
@@ -0,0 +1 @@
|
||||
INSERT OR IGNORE INTO config VALUES ('port', 5000, 'number', 'Port to run server (requires restart)', NULL );
|
||||
Reference in New Issue
Block a user