Author SHA1 Message Date
Manuel83 9b7908c9c5 - Refactoring
- Action Button with Parameter
- Actor Action with Parameter
- Sensor Action with Parameter
2017-11-29 22:37:18 +01:00
Manuel83 13836a5680 - Recipe Book
- BugFixes
- Refactoring
2017-11-22 00:10:11 +01:00
42 changed files with 8699 additions and 275 deletions
+4
View File
@@ -0,0 +1,4 @@
from modules.core.core import CraftBeerPI, Addon
cbpi = CraftBeerPI()
cbpi.addon = Addon(cbpi)
+7 -4
View File
@@ -1,6 +1,8 @@
import json
from flask import request
from flask_classy import FlaskView, route
from modules.core.core import cbpi
from modules import cbpi
class ActionView(FlaskView):
@@ -15,9 +17,10 @@ class ActionView(FlaskView):
200:
description: action invoked
"""
data = request.json
self.cbpi.cache["actions"][action]["function"](self.cbpi)
obj = self.cbpi.cache["actions"][action]["class"](self.cbpi)
obj.execute(**data)
return ('',204)
@cbpi.addon.core.initializer()
@@ -28,4 +31,4 @@ def init(cbpi):
:return: None
"""
ActionView.cbpi = cbpi
ActionView.register(cbpi._app, route_base='/api/action')
ActionView.register(cbpi.web, route_base='/api/action')
+14 -7
View File
@@ -1,14 +1,16 @@
import time
from flask import request
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.core.baseview import BaseView
from modules import cbpi
from modules.core.baseview import RestApi
from modules.database.dbmodel import Actor
class ActorView(BaseView):
class ActorView(RestApi):
model = Actor
cache_key = "actors"
@@ -121,7 +123,7 @@ class ActorView(BaseView):
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.sleep(t)
self.api.cache.get("actors").get(int(id)).timer = None
self.toggle(int(id))
@@ -179,13 +181,18 @@ class ActorView(BaseView):
200:
description: Actor Action called
"""
self.api.actor.action(id, method)
data = request.json
if data:
cbpi.actor.action(id, method, **data)
else:
cbpi.actor.action(id, method)
return ('', 204)
@cbpi.addon.core.initializer(order=1000)
def init(cbpi):
ActorView.register(cbpi._app, route_base='/api/actor')
ActorView.register(cbpi.web, route_base='/api/actor')
ActorView.init_cache()
#cbpi.init_actors()
+33 -7
View File
@@ -1,14 +1,37 @@
from modules.core.baseapi import Buzzer
from modules.core.basetypes import Actor, KettleController, FermenterController
from modules.core.core import cbpi
from modules import cbpi
from modules.core.proptypes import Property
@cbpi.addon.actor.type("Dummy Actor")
class Dummy(Actor):
@cbpi.addon.actor.action("WOHOO")
def myaction(self):
pass
# Decorator to create a parameter based action
@cbpi.addon.actor.action("Run until Temp reached", parameters={"t": Property.Text(label="Target Temp")})
def check_sensor_value(self, t=1):
def check(api, id, value):
'''
Background Prozess which checks the sensor value every second
:param api:
:param id:
:param value:
:return:
'''
while api.sensor.get_value(1) < value:
api.sleep(1)
api.actor.off(id)
target_value = int(t)
# Create notificaiton
self.api.notify(headline="Waiting", message="Waiting for temp %s" % target_value)
# Switch actor on
self.api.actor.on(self.id, 100)
# Start Background task
self.api.start_background_task(check, self.api, id=self.id, value=target_value)
def on(self, power=100):
'''
@@ -16,11 +39,10 @@ class Dummy(Actor):
:param power: int value between 0 - 100
:return:
'''
print "ON"
print "ID %s ON" % self.id
def off(self):
print "OFF"
print "ID %s OFF" % self.id
@cbpi.addon.kettle.controller()
@@ -28,6 +50,7 @@ class MyController(KettleController):
def run(self):
while self.is_running():
print "HALLO"
self.sleep(1)
@@ -38,6 +61,8 @@ class MyController2(FermenterController):
def run(self):
while self.is_running():
print "HALLO"
self.get_target_temp()
self.sleep(1)
@cbpi.addon.core.initializer(order=200)
@@ -45,6 +70,7 @@ def init(cbpi):
class MyBuzzer(Buzzer):
def beep(self):
print "BEEEEEP"
pass
cbpi.buzzer = MyBuzzer()
+34 -14
View File
@@ -3,11 +3,12 @@ import os
from os.path import join
from modules.core.basetypes import Actor, Sensor
from modules.core.core import cbpi
from modules.core.basetypes import Actor, Sensor, Action
from modules import cbpi
from modules.core.proptypes import Property
import random
@cbpi.addon.sensor.type("Dummy Sensor")
class Dummy(Sensor):
@@ -21,10 +22,15 @@ class Dummy(Sensor):
else:
self.unit = "°F"
@cbpi.addon.sensor.action("WOHOO")
def myaction(self):
@cbpi.addon.sensor.action(label="Set Dummy Temp", parameters={
"p1":Property.Select(label="Temp",options=[1,2,3]),
})
def myaction(self, p1):
self.text = p1
self.update_value(int(p1))
print "SENSOR ACTION HALLO!!!"
def execute(self):
while True:
@@ -32,16 +38,30 @@ class Dummy(Sensor):
self.update_value(int(self.text))
except:
pass
self.api.sleep(1)
self.api.sleep(5)
@cbpi.addon.core.action(key="clear", label="Clear all Logs")
def woohoo(cbpi):
dir = "./logs"
test = os.listdir(dir)
@cbpi.addon.core.action(name="Delete All Logs")
class ParameterAction(Action):
for item in test:
p1 = Property.Number("P1", configurable=True, description="Target Temperature of Mash Step", unit="C")
p2 = Property.Number("P2", configurable=True, description="Target Temperature of Mash Step", unit="C")
if item.endswith(".log"):
os.remove(join(dir, item))
cbpi.notify(headline="Logs Deleted",message="All Logs Cleared")
def execute(self, p1, p2, **kwargs):
for i in range(5):
cbpi.sleep(1)
cbpi.notify(headline="Woohoo", message="%s %s" % (p1, p2))
@cbpi.addon.core.action(name="Delete All Logs")
class DeleteAllLogs(Action):
def execute(self, **kwargs):
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")
+226 -3
View File
@@ -1,6 +1,7 @@
from modules.core.basetypes import Step
from modules.core.core import cbpi
from modules import cbpi
from modules.core.proptypes import Property
import time
@cbpi.addon.step.type("Dummy Step")
@@ -17,9 +18,231 @@ class Dummy(Step):
time = Property.Text(label="Text", configurable=True, description="WOHOOO")
def execute(self):
#print self.text
pass
def reset(self):
self.stop_timer()
self.stop_timer()
# -*- coding: utf-8 -*-
@cbpi.addon.step.type("Dummy Step")
class MashStep(Step):
'''
Just put the decorator @cbpi.step on top of a method
'''
# Properties
temp = Property.Number("Temperature", configurable=True, description="Target Temperature of Mash Step", unit="C")
kettle = Property.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.addon.step.action("Start Timer")
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.api.beep()
self.notify("Mash Step Completed!", "Starting the next step", timeout=None)
self.next()
@cbpi.addon.step.type("MashInStep")
class MashInStep(Step):
'''
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 = Property.Kettle("Kettle", description="Kettle in which the mashing takes place")
s = False
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.addon.step.type("MashInStep")
class ChilStep(Step):
timer = Property.Number("Timer in Minutes", configurable=True, default_value=0, description="Timer is started immediately")
@cbpi.addon.step.action("Start 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.addon.step.type("MashInStep")
class PumpStep(Step):
pump = Property.Actor("Pump", description="Pump actor gets toogled")
timer = Property.Number("Timer in Minutes", configurable=True, default_value=0, description="Timer is started immediately")
@cbpi.addon.step.action("Start 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.addon.step.type("MashInStep")
class BoilStep(Step):
'''
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 = Property.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.addon.step.action("Start Timer")
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()
+5 -5
View File
@@ -2,7 +2,7 @@ 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
@@ -16,19 +16,19 @@ class GPIOBuzzer(Buzzer):
def __init__(self, gpio):
try:
cbpi._app.logger.info("INIT BUZZER NOW GPIO%s" % gpio)
cbpi.web.logger.info("INIT BUZZER NOW GPIO%s" % gpio)
self.gpio = int(gpio)
GPIO.setmode(GPIO.BCM)
GPIO.setup(self.gpio, GPIO.OUT)
self.state = True
cbpi._app.logger.info("BUZZER SETUP OK")
cbpi.web.logger.info("BUZZER SETUP OK")
except Exception as e:
cbpi._app.logger.info("BUZZER EXCEPTION %s" % str(e))
cbpi.web.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.web.logger.error("BUZZER not working")
return
def play(sound):
+5 -5
View File
@@ -2,13 +2,13 @@ import time
from flask import json, request
from flask_classy import route
from modules.core.core import cbpi
from modules import cbpi
from modules.core.db import DBModel
from modules.core.baseview import BaseView
from modules.core.baseview import RestApi
from modules.database.dbmodel import Config
class ConfigView(BaseView):
class ConfigView(RestApi):
model = Config
cache_key = "config"
@@ -77,7 +77,7 @@ class ConfigView(BaseView):
@classmethod
def init_cache(cls):
with cls.api._app.app_context():
with cls.api.web.app_context():
cls.api.cache[cls.cache_key] = {}
for key, value in cls.model.get_all().iteritems():
cls.post_init_callback(value)
@@ -85,5 +85,5 @@ class ConfigView(BaseView):
@cbpi.addon.core.initializer(order=0)
def init(cbpi):
ConfigView.register(cbpi._app, route_base='/api/config')
ConfigView.register(cbpi.web, route_base='/api/config')
ConfigView.init_cache()
+62 -26
View File
@@ -22,6 +22,9 @@ class BaseAPI(object):
except:
doc = ""
if self.cbpi.cache.get(key) is None:
self.cbpi.cache[key] = {}
self.cbpi.logger.debug(name)
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("__")]
@@ -29,23 +32,39 @@ class BaseAPI(object):
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})
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, "unit": t.unit})
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):
elif isinstance(t, 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):
elif isinstance(t, 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):
elif isinstance(t, 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):
elif isinstance(t, 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})
parameters = method.__getattribute__("parameters")
props = []
if parameters is not None:
for k, t in parameters.iteritems():
if isinstance(t, Property.Number):
props.append({"name": k, "label": t.label, "type": "number", "configurable": t.configurable, "description": t.description, "default_value": t.default_value, "unit": t.unit})
elif isinstance(t, Property.Text):
props.append({"name": k, "label": t.label, "type": "text", "required": t.required, "configurable": t.configurable, "description": t.description, "default_value": t.default_value})
elif isinstance(t, Property.Select):
props.append({"name": k, "label": t.label, "type": "select", "configurable": True, "options": t.options, "description": t.description})
elif isinstance(t, Property.Actor):
props.append({"name": k, "label": t.label, "type": "actor", "configurable": True, "description": t.description})
elif isinstance(t, Property.Sensor):
props.append({"name": k, "label": t.label, "type": "sensor", "configurable": True, "description": t.description})
elif isinstance(t, Property.Kettle):
props.append({"name": k, "label": t.label, "type": "kettle", "configurable": True, "description": t.description})
self.cbpi.cache.get(key)[name]["actions"].append({"method": method_name, "label": label, "properties":props })
return cls
@@ -59,9 +78,11 @@ class SensorAPI(BaseAPI):
return f
return decorator
def action(self, label):
def action(self, label, parameters=None):
def real_decorator(func):
func.action = True
func.parameters = parameters
func.label = label
return func
return real_decorator
@@ -80,10 +101,11 @@ class StepAPI(BaseAPI):
return f
return decorator
def action(self, label):
def action(self, label, parameters=None):
def real_decorator(func):
func.action = True
func.label = label
func.parameters = parameters
return func
return real_decorator
@@ -100,10 +122,11 @@ class ActorAPI(BaseAPI):
return f
return decorator
def action(self, label):
def action(self, label, parameters=None):
def real_decorator(func):
func.action = True
func.label = label
func.parameters = parameters
return func
return real_decorator
@@ -119,10 +142,11 @@ class KettleAPI(BaseAPI):
return f
return decorator
def action(self, label):
def action(self, label, parameters=None):
def real_decorator(func):
func.action = True
func.label = label
func.parameters = parameters
return func
return real_decorator
@@ -136,10 +160,11 @@ class FermenterAPI(BaseAPI):
return f
return decorator
def action(self, label):
def action(self, label, parameters=None):
def real_decorator(func):
func.action = True
func.label = label
func.parameters = parameters
return func
return real_decorator
@@ -156,7 +181,6 @@ class CoreAPI(BaseAPI):
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"):
@@ -168,12 +192,17 @@ class CoreAPI(BaseAPI):
try:
method(self.cbpi)
except Exception as e:
print e
self.cbpi._socketio.sleep(interval)
self.cbpi.logger.error(e)
self.cbpi.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"))
t = self.cbpi.start_background_task(target=job, interval=value.get("interval"), method=value.get("function"))
def action(self, **options):
def decorator(f):
BaseAPI.parseProps(self, "actions", f)
return f
return decorator
def add_js(self, name, file):
self.cbpi.cache["js"][name] = file
@@ -187,15 +216,7 @@ class CoreAPI(BaseAPI):
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 backgroundtask(self, key, interval, **options):
def decorator(f):
self.cbpi.cache.get("background").append({"function": f, "key": key, "interval": interval})
return f
@@ -220,5 +241,20 @@ class CoreAPI(BaseAPI):
class Buzzer(object):
def beep():
pass
def beep(self):
pass
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(self):
self.core.init()
self.step.init()
self.actor.init()
self.sensor.init()
+26 -4
View File
@@ -12,11 +12,15 @@ class Base(object):
self.value = None
self.__dirty = False
class Action(Base):
def execute(self):
pass
class Actor(Base):
@classmethod
def init_global(cls):
pass
def init(self):
@@ -68,9 +72,6 @@ class Sensor(Base):
print "EXECUTE"
pass
class ControllerBase(object):
__dirty = False
__running = False
@@ -227,6 +228,8 @@ class Timer(object):
return False
class Step(Base, Timer):
@@ -267,12 +270,31 @@ class Step(Base, Timer):
self.value = None
self.__dirty = False
def set_target_temp(self, temp, id=None):
temp = float(temp)
try:
if id is None:
self.api.emit("SET_TARGET_TEMP", id=self.kettle_id, temp=temp)
else:
self.api.emit("SET_TARGET_TEMP", id=id, temp=temp)
except Exception as e:
self.api.notify("Faild to set Target Temp", "", type="warning")
def get_kettle_temp(self, id=None):
id = int(id)
if id is None:
id = self.kettle_id
return self.api.sensor.get_value(int(self.api.cache.get("kettle").get(id).sensor))
def is_dirty(self):
return self.__dirty
def reset_dirty(self):
self.__dirty = False
def notify(self, headline, messsage, timeout=None):
self.api.notify(headline, messsage, timeout)
def __setattr__(self, name, value):
if name != "_StepBase__dirty" and name in self.managed_fields:
+4 -4
View File
@@ -2,10 +2,10 @@ 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):
class RestApi(FlaskView):
as_array = False
cache_key = None
@@ -39,7 +39,7 @@ class BaseView(FlaskView):
def post(self):
data = request.json
self.api._app.logger.info("INSERT Model %s", self.model.__name__)
self.api.web.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:
@@ -101,7 +101,7 @@ class BaseView(FlaskView):
@classmethod
def init_cache(cls):
with cls.api._app.app_context():
with cls.api.web.app_context():
if cls.model.__as_array__ is True:
cls.api.cache[cls.cache_key] = []
+62 -53
View File
@@ -22,7 +22,6 @@ from modules.database.dbmodel import Kettle
class ComplexEncoder(json.JSONEncoder):
def default(self, obj):
try:
if isinstance(obj, DBModel):
return obj.__dict__
elif isinstance(obj, Actor):
@@ -33,31 +32,11 @@ class ComplexEncoder(json.JSONEncoder):
return obj()
else:
return None
return None
except TypeError as e:
pass
return None
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(self):
self.core.init()
self.step.init()
self.actor.init()
self.sensor.init()
# self.kettle.init()
# self.fermenter.init()
class ActorCore(object):
key = "actor_types"
@@ -82,8 +61,7 @@ class ActorCore(object):
actor.power = 100
self.cbpi.emit("INIT_ACTOR", id=id)
except Exception as e:
print e
self.cbpi._app.logger.error(e)
self.cbpi.web.logger.error(e)
def stop_one(self, id):
self.cbpi.cache["actors"][id]["instance"].stop()
@@ -99,7 +77,7 @@ class ActorCore(object):
self.cbpi.emit("SWITCH_ACTOR_ON", id=id, power=power)
return True
except Exception as e:
print e
self.cbpi.logger.error(e)
return False
def off(self, id):
@@ -111,7 +89,7 @@ class ActorCore(object):
self.cbpi.emit("SWITCH_ACTOR_OFF", id=id)
return True
except Exception as e:
print e
self.cbpi.logger.error(e)
return False
def toggle(self, id):
@@ -129,11 +107,11 @@ class ActorCore(object):
self.cbpi.emit("SWITCH_ACTOR_POWER_CHANGE", id=id, power=power)
return True
except Exception as e:
print e
self.cbpi.logger.error(e)
return False
def action(self, id, method):
self.cbpi.cache.get("actors").get(id).instance.__getattribute__(method)()
def action(self, id, method, **data):
self.cbpi.cache.get("actors").get(id).instance.__getattribute__(method)(**data)
def toggle_timeout(self, id, seconds):
@@ -182,7 +160,7 @@ class SensorCore(object):
except Exception as e:
print "ERROR"
self.cbpi._app.logger.error(e)
self.cbpi.web.logger.error(e)
def stop_one(self, id):
@@ -206,12 +184,12 @@ 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)()
def action(self, id, method, **data):
self.cbpi.cache.get("sensors").get(id).instance.__getattribute__(method)(**data)
class BrewingCore(object):
def __init__(self, cbpi):
self.cbpi = cbpi
self.cbpi.cache["step_types"] = {}
@@ -240,7 +218,7 @@ class BrewingCore(object):
# 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))
cfg.update(dict(api=self.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
@@ -250,13 +228,13 @@ class BrewingCore(object):
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.ws_emit("UPDATE_KETTLE", self.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.ws_emit("UPDATE_KETTLE", self.cbpi.cache.get("kettle").get(id))
self.cbpi.emit("KETTLE_CONTROLLER_STOPPED", id=id)
@@ -270,28 +248,50 @@ class FermentationCore(object):
return self.cbpi.cache["fermentation_controller_types"].get(name)
class Logger(object):
def __init__(self, cbpi):
self.cbpi = cbpi
def error(self, msg, *args, **kwargs):
self.cbpi.web.logger.error(msg, *args, **kwargs)
def info(self, msg, *args, **kwargs):
self.cbpi.web.logger.info(msg, *args, **kwargs)
def debug(self, msg, *args, **kwargs):
self.cbpi.web.logger.debug(msg, *args, **kwargs)
def warning(self, msg, *args, **kwargs):
self.cbpi.web.logger.warning(msg, *args, **kwargs)
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 = {}
FORMAT = '%(asctime)-15s - %(levelname)s - %(message)s'
logging.basicConfig(filename='./logs/app.log', level=logging.INFO, format=FORMAT)
logging.getLogger('socketio').setLevel(logging.ERROR)
logging.getLogger('engineio').setLevel(logging.ERROR)
self.web = Flask(__name__)
self.logger = Logger(self)
self.logger.info("###Startup CraftBeerPi %s ###" % self.cache.get("version"))
self.web.secret_key = 'Cr4ftB33rP1'
self.web.json_encoder = ComplexEncoder
self._socketio = SocketIO(self.web, json=json, logging=False)
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('/')
@self.web.route('/')
def index():
return redirect('ui')
@@ -303,11 +303,11 @@ class CraftBeerPI(object):
self.actor.init()
self.beep()
try:
port = int(cbpi.get_config_parameter('port', '5000'))
port = int(self.get_config_parameter('port', '5000'))
except ValueError:
port = 5000
print port
self._socketio.run(self._app, host='0.0.0.0', port=port)
self._socketio.run(self.web, host='0.0.0.0', port=port)
def beep(self):
self.buzzer.beep()
@@ -315,6 +315,10 @@ class CraftBeerPI(object):
def sleep(self, seconds):
self._socketio.sleep(seconds)
def start_background_task(self, target, *args, **kwargs):
self._socketio.start_background_task(target, *args, **kwargs)
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)
@@ -324,10 +328,10 @@ class CraftBeerPI(object):
def __init_db(self, ):
with self._app.app_context():
with self.web.app_context():
db = self.get_db()
try:
with self._app.open_resource('../../config/schema.sql', mode='r') as f:
with self.web.open_resource('../../config/schema.sql', mode='r') as f:
db.cursor().executescript(f.read())
db.commit()
except Exception as e:
@@ -364,15 +368,23 @@ class CraftBeerPI(object):
method.callback = True
self.cache[key] = method
def get_config_parameter(self, key, default):
def get_config_parameter(self, key, default=None):
cfg = self.cache["config"].get(key)
if cfg is None:
return default
else:
return cfg.value
def emit(self, key, **kwargs):
def set_config_parameter(self, name, value):
from modules.config import Config
with self.web.app_context():
update_data = {"name": name, "value": value}
self.cache.get("config")[name].__dict__.update(**update_data)
c = Config.update(**update_data)
self.ws_emit("UPDATE_CONFIG", c)
def emit(self, key, **kwargs):
if self.eventbus.get(key) is not None:
for value in self.eventbus[key]:
if value["async"] is False:
@@ -391,6 +403,3 @@ class CraftBeerPI(object):
self.notify("Failed to load plugin %s " % filename, str(e), type="danger", timeout=None)
cbpi = CraftBeerPI()
addon = cbpi.addon
+5 -6
View File
@@ -1,11 +1,11 @@
import sqlite3
import os
from modules.core.core import cbpi
from modules import cbpi
from modules.core.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.web.logger.info("SKIP DB FILE: %s" % data["file"])
return
try:
with sqlite3.connect("craftbeerpi.db") as conn:
@@ -18,14 +18,13 @@ def execute_file(curernt_version, data):
cur.execute("INSERT INTO schema_info (version,filename) values (?,?)", (data["version"], data["file"]))
conn.commit()
except sqlite3.OperationalError as err:
print err
except sqlite3.OperationalError as e:
cbpi.logger.error(e)
@cbpi.addon.core.initializer(order=-9999)
def init(cbpi):
with cbpi._app.app_context():
with cbpi.web.app_context():
conn = get_db()
cur = conn.cursor()
current_version = None
+2
View File
@@ -16,6 +16,7 @@ class Property(object):
self.configurable = configurable
self.default_value = default_value
self.description = description
self.unit = unit
class Text(PropertyType):
def __init__(self, label, configurable=False, required=False, default_value="", description=""):
@@ -44,5 +45,6 @@ class Property(object):
def __init__(self, label, description=""):
PropertyType.__init__(self)
self.label = label
self.unit = ""
self.configurable = True
self.description = description
+2 -1
View File
@@ -131,4 +131,5 @@ class FermenterStep(DBModel):
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()
get_db().commit()
@@ -1,11 +1,11 @@
from flask import Blueprint
from modules.core.core import cbpi, addon
from modules import cbpi
from flask_swagger import swagger
from flask import json
from flask import Blueprint
@addon.core.initializer(order=22)
@cbpi.addon.core.initializer(order=22)
def web(cbpi):
s = Blueprint('web_view', __name__, template_folder='templates', static_folder='static')
@@ -16,4 +16,4 @@ def web(cbpi):
cbpi.addon.core.add_menu_link("JQuery View", "/web_view")
cbpi._app.register_blueprint(s, url_prefix='/web_view')
cbpi.web.register_blueprint(s, url_prefix='/web_view')
@@ -1,11 +1,11 @@
from flask import Blueprint
from modules.core.core import cbpi, addon
from modules import cbpi
from flask_swagger import swagger
from flask import json
from flask import Blueprint
@addon.core.initializer(order=22)
@cbpi.addon.core.initializer(order=22)
def web(cbpi):
s = Blueprint('webviewreact', __name__, template_folder='templates', static_folder='static')
@@ -16,4 +16,4 @@ def web(cbpi):
cbpi.addon.core.add_menu_link("ReactJS View", "/webviewreact")
cbpi._app.register_blueprint(s, url_prefix='/webviewreact')
cbpi.web.register_blueprint(s, url_prefix='/webviewreact')
+4 -4
View File
@@ -1,9 +1,9 @@
from modules.core.core import cbpi, addon
from modules import cbpi
from flask_swagger import swagger
from flask import json
from flask import Blueprint
@addon.core.initializer(order=22)
@cbpi.addon.core.initializer(order=22)
def hello(cbpi):
s = Blueprint('react', __name__, template_folder='templates', static_folder='static')
@@ -14,10 +14,10 @@ def hello(cbpi):
@s.route('/swagger.json', methods=["GET"])
def spec():
swag = swagger(cbpi._app)
swag = swagger(cbpi.web)
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')
cbpi.web.register_blueprint(s, url_prefix='/swagger')
+15 -10
View File
@@ -2,13 +2,13 @@ import time
from flask import request
from flask_classy import route
from modules.core.core import cbpi
from modules import cbpi
from modules.core.db import get_db, DBModel
from modules.core.baseview import BaseView
from modules.core.baseview import RestApi
from modules.database.dbmodel import Fermenter, FermenterStep
class FermenterView(BaseView):
class FermenterView(RestApi):
model = Fermenter
cache_key = "fermenter"
@@ -106,6 +106,7 @@ class FermenterView(BaseView):
@route('/<int:id>/start', methods=['POST'])
def start_fermentation(self, id):
print "START"
active = None
for idx, s in enumerate(cbpi.cache.get(self.cache_key)[id].steps):
if s.state == 'A':
@@ -196,6 +197,7 @@ class FermenterView(BaseView):
return cbpi.cache["fermenter"].get(id)
def target_temp_reached(self,id, step):
print "TARGET TEMP REACHED"
timestamp = time.time()
days = step.days * 24 * 60 * 60
@@ -210,19 +212,21 @@ class FermenterView(BaseView):
cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id])
def check_step(self):
for key, value in cbpi.cache["fermenter_task"].iteritems():
try:
fermenter = self.get_fermenter(key)
current_temp = current_temp = cbpi.get_sensor_value(int(fermenter.sensor))
current_temp = current_temp = cbpi.sensor.get_value(int(fermenter.sensor))
if value.timer_start is None:
print "TIMER IS NONE"
if value.direction == "H" :
print "TIMER WATING FOR HEATING"
if current_temp >= value.temp:
self.target_temp_reached(key,value)
else:
print "TIMER WATING FOR COILING"
if current_temp <= value.temp:
self.target_temp_reached(key, value)
else:
@@ -231,10 +235,11 @@ class FermenterView(BaseView):
else:
pass
except Exception as e:
self.api.looger.error(e)
pass
@cbpi.addon.core.backgroundjob(key="read_target_temps_fermenter", interval=5)
@cbpi.addon.core.backgroundtask(key="read_target_temps_fermenter", interval=5)
def read_target_temps(cbpi):
"""
background process that reads all passive sensors in interval of 1 second
@@ -247,9 +252,9 @@ def read_target_temps(cbpi):
instance = FermenterView()
@cbpi.addon.core.backgroundjob(key="fermentation_task", interval=1)
@cbpi.addon.core.backgroundtask(key="fermentation_task", interval=1)
def execute_fermentation_step(cbpi):
with cbpi._app.app_context():
with cbpi.web.app_context():
instance.check_step()
@@ -262,5 +267,5 @@ def init_active_steps():
def init(cbpi):
cbpi.cache["fermenter_task"] = {}
FermenterView.register(cbpi._app, route_base='/api/fermenter')
FermenterView.register(cbpi.web, route_base='/api/fermenter')
FermenterView.init_cache()
+16 -5
View File
@@ -1,11 +1,11 @@
from flask import request
from flask_classy import FlaskView, route
from modules.core.core import cbpi
from modules.core.baseview import BaseView
from modules import cbpi
from modules.core.baseview import RestApi
from modules.core.db import DBModel
from modules.database.dbmodel import Kettle
class KettleView(BaseView):
class KettleView(RestApi):
model = Kettle
cache_key = "kettle"
@@ -217,7 +217,18 @@ class KettleView(BaseView):
self.api.brewing.toggle_automatic(id)
return ('', 204)
@cbpi.addon.core.backgroundjob(key="read_target_temps", interval=5)
@cbpi.addon.core.listen("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
'''
KettleView().postTargetTemp(id,temp)
@cbpi.addon.core.backgroundtask(key="read_target_temps", interval=5)
def read_target_temps(api):
"""
background process that reads all passive sensors in interval of 1 second
@@ -230,5 +241,5 @@ def read_target_temps(api):
@cbpi.addon.core.initializer()
def init(cbpi):
KettleView.api = cbpi
KettleView.register(cbpi._app, route_base='/api/kettle')
KettleView.register(cbpi.web, route_base='/api/kettle')
KettleView.init_cache()
+24 -7
View File
@@ -1,18 +1,19 @@
import flask_login
from flask import request
from modules.core.core import cbpi, addon
from modules import cbpi
class User(flask_login.UserMixin):
pass
@addon.core.initializer(order=0)
@cbpi.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'])
cbpi._login_manager.init_app(cbpi.web)
@cbpi.web.route('/login', methods=['POST'])
def login():
data = request.json
@@ -30,16 +31,30 @@ def log(cbpi):
return ('',401)
@cbpi._app.route('/logout', methods=['POST'])
@cbpi.web.route('/logout', methods=['POST'])
def logout():
flask_login.logout_user()
return 'Logged out'
@cbpi._login_manager.request_loader
def load_user_from_request(request):
api_key = request.args.get('api_key')
if cbpi.get_config_parameter("password_security", "NO") == "NO":
user = User()
user.id = "craftbeerpi"
return user
elif api_key == "123":
user = User()
user.id = "craftbeerpi"
return user
return None
@cbpi._login_manager.user_loader
def user_loader(user):
if cbpi.get_config_parameter("password_security", "NO") == "YES":
if user != "craftbeerpi":
return
@@ -53,4 +68,6 @@ def log(cbpi):
@cbpi._login_manager.unauthorized_handler
def unauthorized_handler():
return ('Please login',401)
+2 -2
View File
@@ -2,7 +2,7 @@ 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
from modules import cbpi
class LogView(FlaskView):
@@ -172,4 +172,4 @@ def init(cbpi):
:param app: the flask app
:return: None
"""
LogView.register(cbpi._app, route_base='/api/logs')
LogView.register(cbpi.web, route_base='/api/logs')
+2 -2
View File
@@ -1,6 +1,6 @@
import json
from flask_classy import FlaskView, route
from modules.core.core import cbpi
from modules import cbpi
class NotificationView(FlaskView):
@@ -63,4 +63,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.web, route_base='/api/notification')
+3 -2
View File
@@ -1,7 +1,7 @@
import sys
from flask import request, send_from_directory, json
from importlib import import_module
from modules.core.core import cbpi
from modules import cbpi
from git import Repo
import os
import requests
@@ -46,6 +46,7 @@ class PluginView(FlaskView):
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"])
@@ -135,4 +136,4 @@ class PluginView(FlaskView):
def init(cbpi):
cbpi.cache["plugins"] = {}
PluginView.api = cbpi
PluginView.register(cbpi._app, route_base='/api/plugin')
PluginView.register(cbpi.web, route_base='/api/plugin')
+146
View File
@@ -0,0 +1,146 @@
import os
import re
import time
import datetime
from flask import json, request, send_from_directory
from flask_classy import route, FlaskView
from modules.core.db import DBModel
from modules.core.baseview import RestApi
from modules import cbpi
from modules.database.dbmodel import Step
from yaml import Loader, Dumper
from yaml import load, dump
from modules.step import StepView
class RecipeBook(FlaskView):
@route('/load', methods=["POST"])
def load(self):
data = request.json
recipe_name = data.get("name")
if re.match("^[A-Za-z0-9_-]*$", recipe_name) is None:
return ('Recipie Name contains not allowed characters', 500)
with open("./recipes/%s.json" % recipe_name) as json_data:
d = json.load(json_data)
Step.delete_all()
StepView().reset()
for s in d["steps"]:
Step.insert(**{"name": s.get("name"), "type": s.get("type"), "config": s.get("config")})
self.api.ws_emit("UPDATE_ALL_STEPS", Step.get_all())
self.api.notify(headline="Recipe %s loaded successfully" % recipe_name, message="")
return ('', 204)
@route('/download/<name>', methods=["GET"])
def download(self, name):
"""
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
"""
file = "%s.json" % name
if not self.check_filename(file):
return ('File Not Found111', 404)
return send_from_directory('../../recipes', file, as_attachment=True, attachment_filename=file)
def check_filename(self, name):
import re
print "CHECK"
pattern = re.compile('^([A-Za-z0-9-_])+.json$')
return True if pattern.match(name) else False
@route('/<name>', methods=["DELETE"])
def remove(self, name):
recipe_name = name
if re.match("^[A-Za-z0-9_-]*$", recipe_name) is None:
return ('Recipie Name contains not allowed characters', 500)
filename = "./recipes/%s.json" % recipe_name
if os.path.isfile(filename) == True:
os.remove(filename)
self.api.notify(headline="Recipe %s deleted successfully" % recipe_name, message="")
return ('', 204)
else:
self.api.notify(headline="Faild to delete Recipe %s deleted" % recipe_name, message="")
return ('', 404)
return ('', 204)
@route('/', methods=["GET"])
def get_all(self):
result = []
for filename in os.listdir("./recipes"):
if filename.endswith(".json"):
result.append({"id":filename.split(".")[0], "name": filename.split(".")[0], "change_date": self._modification_date('./recipes/%s' % filename)})
return json.dumps(result)
def _modification_date(self, filename):
t = os.path.getmtime(filename)
return datetime.datetime.fromtimestamp(t).strftime('%Y-%m-%d %H:%M:%S')
@route('/save', methods=["POST"])
def save(self):
"""
Save Recepie
---
tags:
- steps
responses:
204:
description: Recipe saved
"""
recipe_name = self.api.get_config_parameter("brew_name")
if recipe_name is None or len(recipe_name) <= 0:
self.api.notify(headline="Please set brew name", message="Recipe not saved!", type="danger")
return ('Recipie Name contains not allowed characters', 500)
if re.match("^[\sA-Za-z0-9_-]*$", recipe_name) is None:
self.api.notify(headline="Only alphanummeric charaters are allowd for recipe name", message="", type="danger")
return ('Recipie Name contains not allowed characters', 500)
recipe_data = {"name": recipe_name, "steps": Step.get_all()}
file_name = recipe_name.replace(" ", "_")
with open('./recipes/%s.json' % file_name, 'w') as outfile:
json.dump(recipe_data, outfile, indent=4)
self.api.notify(headline="Recipe %s saved successfully" % recipe_name, message="")
return ('', 204)
@cbpi.addon.core.initializer(order=2000)
def init(cbpi):
RecipeBook.api = cbpi
RecipeBook.register(cbpi.web, route_base='/api/recipebook')
+11 -12
View File
@@ -2,7 +2,7 @@ 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 import cbpi
from werkzeug.utils import secure_filename
import pprint
import time
@@ -55,11 +55,12 @@ class BeerXMLImport(FlaskView):
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"))
file.save(os.path.join(self.api.get_config_parameter('UPLOAD_FOLDER', "./upload"), "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.logger.error(e)
self.api.notify(headline="Upload Failed", message="Failed to upload Beer xml", type="danger")
return ('', 500)
@@ -88,12 +89,12 @@ class BeerXMLImport(FlaskView):
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)
mashstep_type = self.api.get_config_parameter("step_mash", "MashStep")
mash_kettle = self.api.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
boilstep_type = self.api.get_config_parameter("step_boil", "BoilStep")
boil_kettle = self.api.get_config_parameter("step_boil_kettle", None)
boil_temp = 100 if self.api.get_config_parameter("unit", "C") == "C" else 212
# READ KBH DATABASE
Step.delete_all()
@@ -108,7 +109,8 @@ class BeerXMLImport(FlaskView):
Step.insert(**{"name": "Boil", "type": boilstep_type, "config": {"kettle": boil_kettle, "temp": boil_temp, "timer": boil_time}})
## Add Whirlpool step
Step.insert(**{"name": "Whirlpool", "type": "ChilStep", "config": {"timer": 15}})
self.api.emit("UPDATE_ALL_STEPS", Step.get_all())
self.api.ws_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")
@@ -125,9 +127,6 @@ class BeerXMLImport(FlaskView):
return float(e.find('./RECIPE[%s]/BOIL_TIME' % (str(id))).text)
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))):
@@ -144,4 +143,4 @@ class BeerXMLImport(FlaskView):
def init(cbpi):
BeerXMLImport.api = cbpi
BeerXMLImport.register(cbpi._app, route_base='/api/beerxml')
BeerXMLImport.register(cbpi.web, route_base='/api/beerxml')
+3 -3
View File
@@ -2,7 +2,7 @@ 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 import cbpi
from werkzeug.utils import secure_filename
import pprint
import time
@@ -39,7 +39,7 @@ class KBH(FlaskView):
result.append({"id": row[0], "name": row[1], "brewed": row[2]})
return json.dumps(result)
except Exception as e:
print e
self.api.logger.error(e)
self.api.notify(headline="Failed to load KHB database", message="ERROR", type="danger")
return ('', 500)
finally:
@@ -143,4 +143,4 @@ class KBH(FlaskView):
def init(cbpi):
KBH.api = cbpi
KBH.register(cbpi._app, route_base='/api/kbh')
KBH.register(cbpi.web, route_base='/api/kbh')
+2 -2
View File
@@ -2,7 +2,7 @@ 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 import cbpi
from werkzeug.utils import secure_filename
import pprint
import time
@@ -59,4 +59,4 @@ class RESTImport(FlaskView):
@cbpi.addon.core.initializer()
def init(cbpi):
RESTImport.api = cbpi
RESTImport.register(cbpi._app, route_base='/api/recipe/import/v1')
RESTImport.register(cbpi.web, route_base='/api/recipe/import/v1')
+16 -10
View File
@@ -1,12 +1,12 @@
import time
from flask_classy import route
from modules.core.core import cbpi
from modules import cbpi
from modules.core.db import DBModel
from modules.core.baseview import BaseView
from modules.core.baseview import RestApi
from modules.database.dbmodel import Sensor
from flask import request
class SensorView(BaseView):
class SensorView(RestApi):
model = Sensor
cache_key = "sensors"
@@ -34,7 +34,11 @@ class SensorView(BaseView):
200:
description: Sensor Action called
"""
cbpi.sensor.action(id, method)
data = request.json
if data:
cbpi.sensor.action(id, method, **data)
else:
cbpi.sensor.action(id, method)
return ('', 204)
def _post_post_callback(self, m):
@@ -51,17 +55,19 @@ class SensorView(BaseView):
@cbpi.addon.core.initializer(order=1000)
def init(cbpi):
SensorView.register(cbpi._app, route_base='/api/sensor')
SensorView.register(cbpi.web, route_base='/api/sensor')
SensorView.init_cache()
#@cbpi.backgroundtask(key="read_passiv_sensor", interval=5)
#@cbpi.addon.core.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()
#for key, value in cbpi.cache.get("sensors").iteritems():
# if value.mode == "P":
# value.instance.read()
+10 -8
View File
@@ -3,12 +3,12 @@ from flask import json, request
from flask_classy import route
from modules.core.db import DBModel
from modules.core.baseview import BaseView
from modules.core.core import cbpi
from modules.core.baseview import RestApi
from modules import cbpi
from modules.database.dbmodel import Step
class StepView(BaseView):
class StepView(RestApi):
model = Step
def _pre_post_callback(self, data):
order = self.model.get_max_order()
@@ -43,6 +43,7 @@ class StepView(BaseView):
"""
self.model.delete_all()
self.api.emit("ALL_BREWING_STEPS_DELETED")
self.api.set_config_parameter("brew_name", "")
cbpi.ws_emit("UPDATE_ALL_STEPS", self.model.get_all())
return ('', 204)
@@ -206,24 +207,25 @@ def init_after_startup():
@cbpi.addon.core.initializer(order=2000)
def init(cbpi):
StepView.register(cbpi._app, route_base='/api/step')
StepView.register(cbpi.web, route_base='/api/step')
def get_all():
with cbpi._app.app_context():
with cbpi.web.app_context():
return Step.get_all()
with cbpi._app.app_context():
with cbpi.web.app_context():
init_after_startup()
cbpi.add_cache_callback("steps", get_all)
@cbpi.addon.core.backgroundjob(key="step_task", interval=0.1)
@cbpi.addon.core.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.web.app_context():
step = cbpi.cache.get("active_step")
if step is not None:
step.execute()
+2 -2
View File
@@ -5,7 +5,7 @@ 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
from modules import cbpi
import pprint
import time
@@ -190,4 +190,4 @@ class SystemView(FlaskView):
def init(cbpi):
SystemView.api = cbpi
SystemView.register(cbpi._app, route_base='/api/system')
SystemView.register(cbpi.web, route_base='/api/system')
+3 -3
View File
@@ -1,12 +1,12 @@
from flask import Blueprint,render_template
from modules.core.core import cbpi
from modules 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')
cbpi.web.register_blueprint(react, url_prefix='/ui')
@react.route('/', methods=["GET"])
@@ -22,7 +22,7 @@ def index():
@cbpi._app.errorhandler(404)
@cbpi.web.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404
Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 941 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 KiB

+7713 -2
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+6
View File
@@ -10,11 +10,17 @@
<link rel="stylesheet" href="static/bootstrap.dark.css">
<style>
html {
background-image: url('static/bg.png');
}
</style>
<title>CraftBeerPi 3.0</title>
</head>
<body>
<div id="root" ></div>
HALLO
<script src="static/bundle.js" type="text/javascript"></script>
+4 -9
View File
@@ -7,18 +7,13 @@
<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>
<link rel="stylesheet" href="static/theme.css">
<link href="https://fonts.googleapis.com/css?family=Dosis:200,500" rel="stylesheet"/>
<title>CraftBeerPi 3.1</title>
</head>
<body>
<div id="root" ></div>
View File
+2 -3
View File
@@ -1,10 +1,8 @@
#!/usr/bin/env python
from modules.core.core import *
cbpi = CraftBeerPI()
addon = cbpi.addon
from modules import cbpi
from modules.core.db_migrate import *
@@ -29,5 +27,6 @@ from modules.base_plugins.steps import *
from modules.example_plugins.WebViewJquery import *
from modules.example_plugins.WebViewReactJs import *
from modules.example_plugins.swagger import *
from modules.recipe_book import *
cbpi.run()
+173
View File
@@ -0,0 +1,173 @@
<?xml version="1.0" encoding="UTF-8"?>
<RECIPES>
<RECIPE>
<NAME>Pale Ale</NAME>
<VERSION>1</VERSION>
<TYPE>All Grain</TYPE>
<BREWER></BREWER>
<DISPLAY_BATCH_SIZE>6.5 gal</DISPLAY_BATCH_SIZE>
<DISPLAY_BOIL_SIZE>7.5 gal</DISPLAY_BOIL_SIZE>
<BATCH_SIZE>24.60517657</BATCH_SIZE>
<BOIL_SIZE>28.39058835</BOIL_SIZE>
<BOIL_TIME>60</BOIL_TIME>
<EFFICIENCY>63</EFFICIENCY>
<NOTES>&#13;
</NOTES>
<PRIMARY_TEMP>20</PRIMARY_TEMP>
<EST_COLOR>8.75</EST_COLOR>
<IBU>62.22</IBU>
<IBU_METHOD>Tinseth</IBU_METHOD>
<EST_ABV>4.88</EST_ABV>
<EST_OG>1.047 sg</EST_OG>
<EST_FG>1.01 sg</EST_FG>
<OG>1.047</OG>
<FG>1.01</FG>
<PRIMING_SUGAR_NAME></PRIMING_SUGAR_NAME>
<CARBONATION_USED></CARBONATION_USED>
<BF_PRIMING_METHOD></BF_PRIMING_METHOD>
<BF_PRIMING_AMOUNT></BF_PRIMING_AMOUNT>
<BF_CO2_LEVEL></BF_CO2_LEVEL>
<BF_CO2_UNIT>Volumes</BF_CO2_UNIT>
<URL></URL>
<BATCH_SIZE_MODE>f</BATCH_SIZE_MODE>
<YEAST_STARTER>false</YEAST_STARTER>
<NO_CHILL_EXTRA_MINUTES></NO_CHILL_EXTRA_MINUTES>
<STARTING_MASH_THICKNESS>3.33880656</STARTING_MASH_THICKNESS>
<PITCH_RATE>0.35</PITCH_RATE>
<FERMENTABLES>
<FERMENTABLE>
<NAME>Pale 2-Row</NAME>
<VERSION>1</VERSION>
<TYPE>Grain</TYPE>
<AMOUNT>4.08233133</AMOUNT>
<YIELD>80.43</YIELD>
<COLOR>1.8</COLOR>
<ADD_AFTER_BOIL>false</ADD_AFTER_BOIL>
<ORIGIN>American</ORIGIN>
</FERMENTABLE>
<FERMENTABLE>
<NAME>Caramel / Crystal 60L</NAME>
<VERSION>1</VERSION>
<TYPE>Grain</TYPE>
<AMOUNT>0.45359237</AMOUNT>
<YIELD>73.91</YIELD>
<COLOR>60</COLOR>
<ADD_AFTER_BOIL>false</ADD_AFTER_BOIL>
<ORIGIN>American</ORIGIN>
</FERMENTABLE>
<FERMENTABLE>
<NAME>Rye</NAME>
<VERSION>1</VERSION>
<TYPE>Grain</TYPE>
<AMOUNT>1.0205828325</AMOUNT>
<YIELD>82.61</YIELD>
<COLOR>3.5</COLOR>
<ADD_AFTER_BOIL>false</ADD_AFTER_BOIL>
<ORIGIN>American</ORIGIN>
</FERMENTABLE>
<FERMENTABLE>
<NAME>Flaked Wheat</NAME>
<VERSION>1</VERSION>
<TYPE>Adjunct</TYPE>
<AMOUNT>0.226796185</AMOUNT>
<YIELD>73.91</YIELD>
<COLOR>2</COLOR>
<ADD_AFTER_BOIL>false</ADD_AFTER_BOIL>
<ORIGIN></ORIGIN>
</FERMENTABLE>
<FERMENTABLE>
<NAME>Carapils</NAME>
<VERSION>1</VERSION>
<TYPE>Grain</TYPE>
<AMOUNT>0.226796185</AMOUNT>
<YIELD>76.09</YIELD>
<COLOR>1.3</COLOR>
<ADD_AFTER_BOIL>false</ADD_AFTER_BOIL>
<ORIGIN>German</ORIGIN>
</FERMENTABLE>
</FERMENTABLES>
<HOPS>
<HOP>
<NAME>El Dorado</NAME>
<VERSION>1</VERSION>
<ALPHA>15.7</ALPHA>
<AMOUNT>0.0283495231</AMOUNT>
<USE>First Wort</USE>
<USER_HOP_USE>First Wort</USER_HOP_USE>
<TIME>0</TIME>
<FORM>Leaf</FORM>
</HOP>
<HOP>
<NAME>El Dorado</NAME>
<VERSION>1</VERSION>
<ALPHA>15.7</ALPHA>
<AMOUNT>0.0283495231</AMOUNT>
<USE>Boil</USE>
<USER_HOP_USE>Boil</USER_HOP_USE>
<TIME>30</TIME>
<FORM>Leaf</FORM>
</HOP>
<HOP>
<NAME>El Dorado</NAME>
<VERSION>1</VERSION>
<ALPHA>15.7</ALPHA>
<AMOUNT>0.0283495231</AMOUNT>
<USE>Dry Hop</USE>
<USER_HOP_USE>Dry Hop</USER_HOP_USE>
<TIME>14400</TIME>
<FORM>Leaf</FORM>
</HOP>
</HOPS>
<MISCS/>
<MASH>
<NAME>Mash Steps</NAME>
<VERSION>1</VERSION>
<GRAIN_TEMP>20</GRAIN_TEMP>
<MASH_STEPS>
<MASH_STEP>
<NAME>STEP1</NAME>
<VERSION>1</VERSION>
<TYPE>Infusion</TYPE>
<STEP_TIME>60</STEP_TIME>
<INFUSE_AMOUNT>1.5141647136</INFUSE_AMOUNT>
<STEP_TEMP>65.555555555556</STEP_TEMP>
</MASH_STEP>
</MASH_STEPS>
</MASH>
<YEASTS>
<YEAST>
<NAME>California Ale Yeast WLP001</NAME>
<VERSION>1</VERSION>
<TYPE>Ale</TYPE>
<FORM>Liquid</FORM>
<AMOUNT>0.1</AMOUNT>
<PRODUCT_ID>WLP001</PRODUCT_ID>
<LABORATORY>White Labs</LABORATORY>
<ATTENUATION>76.5</ATTENUATION>
<FLOCCULATION>Medium</FLOCCULATION>
<MIN_TEMPERATURE>20</MIN_TEMPERATURE>
<MAX_TEMPERATURE>22.777777777778</MAX_TEMPERATURE>
</YEAST>
</YEASTS>
<WATERS/>
<STYLE>
<NAME>English IPA</NAME>
<VERSION>1</VERSION>
<CATEGORY>India Pale Ale (IPA)</CATEGORY>
<CATEGORY_NUMBER>14</CATEGORY_NUMBER>
<STYLE_LETTER>A</STYLE_LETTER>
<STYLE_GUIDE>BJCP</STYLE_GUIDE>
<TYPE>Ale</TYPE>
<OG_MIN>1.05</OG_MIN>
<OG_MAX>1.075</OG_MAX>
<FG_MIN>1.01</FG_MIN>
<FG_MAX>1.018</FG_MAX>
<ABV_MIN>5</ABV_MIN>
<ABV_MAX>7.5</ABV_MAX>
<IBU_MIN>40</IBU_MIN>
<IBU_MAX>60</IBU_MAX>
<COLOR_MIN>8</COLOR_MIN>
<COLOR_MAX>14</COLOR_MAX>
</STYLE>
</RECIPE>
</RECIPES>