Pārlūkot izejas kodu

Merge pull request #1 from Manuel83/master

Merge
tags/3.1_alpha
IndyJoeA GitHub pirms 8 gadiem
vecāks
revīzija
fc04ae01a7
46 mainītis faili ar 6911 papildinājumiem un 226 dzēšanām
  1. +4
    -0
      .gitignore
  2. +6
    -0
      README.md
  3. +18
    -15
      config/schema.sql
  4. +1
    -0
      config/version.yaml
  5. +5
    -0
      modules/__init__.py
  6. +9
    -4
      modules/actor/__init__.py
  7. +6
    -4
      modules/addon/endpoints.py
  8. +4
    -2
      modules/app_config.py
  9. +132
    -6
      modules/base_plugins/brew_steps/__init__.py
  10. +24
    -7
      modules/base_plugins/dummy_temp/__init__.py
  11. +9
    -9
      modules/base_plugins/fermenter_hysteresis/__init__.py
  12. +17
    -3
      modules/base_plugins/gpio_actor/__init__.py
  13. +4
    -6
      modules/base_plugins/hysteresis/__init__.py
  14. +9
    -4
      modules/base_plugins/one_wire/__init__.py
  15. +1
    -3
      modules/buzzer/__init__.py
  16. +3
    -5
      modules/config/__init__.py
  17. +15
    -15
      modules/core/baseview.py
  18. +2
    -2
      modules/core/controller.py
  19. +56
    -23
      modules/core/core.py
  20. +4
    -2
      modules/core/db.py
  21. +47
    -0
      modules/core/db_mirgrate.py
  22. +11
    -7
      modules/core/hardware.py
  23. +36
    -7
      modules/core/props.py
  24. +5
    -5
      modules/core/step.py
  25. +7
    -6
      modules/fermenter/__init__.py
  26. +6
    -6
      modules/kettle/__init__.py
  27. +0
    -1
      modules/logs/endpoints.py
  28. +6
    -8
      modules/notification/__init__.py
  29. +3
    -0
      modules/recipe_import/__init__.py
  30. +110
    -0
      modules/recipe_import/beerxml.py
  31. +109
    -0
      modules/recipe_import/kbh.py
  32. +62
    -0
      modules/recipe_import/restapi.py
  33. +10
    -5
      modules/sensors/__init__.py
  34. +1
    -1
      modules/stats/__init__.py
  35. +24
    -15
      modules/steps/__init__.py
  36. +40
    -2
      modules/system/endpoints.py
  37. +3
    -2
      modules/ui/endpoints.py
  38. +6006
    -0
      modules/ui/package-lock.json
  39. +16
    -0
      modules/ui/src/App.css
  40. +5
    -0
      modules/ui/src/index.css
  41. +48
    -48
      modules/ui/static/bundle.js
  42. +7
    -1
      modules/ui/static/index.html
  43. +1
    -2
      requirements.txt
  44. +18
    -0
      update/1_schema_info.sql
  45. +1
    -0
      update/2_more_buzzer.sql
  46. +0
    -0
      update/empty

+ 4
- 0
.gitignore Parādīt failu

@@ -8,3 +8,7 @@ craftbeerpi.db
modules/plugins/*
!modules/plugins/__init__.py
*.pyc
*.js
modules/ui/package.json

modules/ui/.babelrc

+ 6
- 0
README.md Parādīt failu

@@ -18,6 +18,12 @@ 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.


+ 18
- 15
config/schema.sql Parādīt failu

@@ -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,19 +79,22 @@ 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]');
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),
type VARCHAR(100),
config VARCHAR(500),
hide BOOLEAN
);
);

+ 1
- 0
config/version.yaml Parādīt failu

@@ -0,0 +1 @@
3.0.2

+ 5
- 0
modules/__init__.py Parādīt failu

@@ -35,6 +35,8 @@ 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
@@ -63,3 +65,6 @@ cbpi.run_background_processes()
app.logger.info("##########################################")
app.logger.info("### STARTUP COMPLETE")
app.logger.info("##########################################")

+ 9
- 4
modules/actor/__init__.py Parādīt failu

@@ -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()

+ 6
- 4
modules/addon/endpoints.py Parādīt failu

@@ -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))
@@ -188,11 +190,11 @@ def loadPlugins():

@cbpi.initalizer(order=1)
def initPlugins(app):
print "INITIALIZE CUSTOM PLUGINS"
loadCorePlugins()
loadPlugins()

@cbpi.initalizer(order=2)
def init(cbpi):
print "INITIALIZE ADDON MODULE"
cbpi.app.register_blueprint(blueprint, url_prefix='/api/editor')

+ 4
- 2
modules/app_config.py Parādīt failu

@@ -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("##########################################")


+ 132
- 6
modules/base_plugins/brew_steps/__init__.py Parādīt failu

@@ -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,7 +50,7 @@ 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)
@@ -65,10 +66,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
@@ -93,3 +98,124 @@ class MashInStep(StepBase):



@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="Second 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) >= int(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)
# Check if timer finished and go to next step
if self.is_timer_finished() == True:
self.next()

+ 24
- 7
modules/base_plugins/dummy_temp/__init__.py Parādīt failu

@@ -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:
'''





+ 9
- 9
modules/base_plugins/fermenter_hysteresis/__init__.py Parādīt failu

@@ -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)
self.sleep(1)

+ 17
- 3
modules/base_plugins/gpio_actor/__init__.py Parādīt failu

@@ -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,7 +35,7 @@ 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])
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")
duty_cylce = Property.Number("Duty Cycle", configurable=True)

p = None
@@ -57,6 +57,11 @@ class GPIOPWM(ActorBase):
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"




+ 4
- 6
modules/base_plugins/hysteresis/__init__.py Parādīt failu

@@ -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):
'''
@@ -30,11 +30,9 @@ class Hysteresis(KettleController):
'''
while self.is_running():

self.actor_power(50)

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):
elif self.get_temp() >= self.get_target_temp() - float(self.off):
self.heater_off()
else:
self.heater_off()


+ 9
- 4
modules/base_plugins/one_wire/__init__.py Parādīt failu

@@ -66,7 +66,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,10 +87,14 @@ class ONE_WIRE_SENSOR(SensorPassive):

def read(self):
if self.get_config_parameter("unit", "C") == "C":
self.data_received(round(self.t.value, 2))
self.data_received(round(self.t.value + self.offset_value(), 2))
else:
self.data_received(round(9.0 / 5.0 * self.t.value + 32, 2))
self.data_received(round(9.0 / 5.0 * self.t.value + 32 + self.offset_value(), 2))

@cbpi.try_catch(0)
def offset_value(self):
return float(self.offset)
@classmethod
def init_global(self):
try:
@@ -108,5 +113,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')

+ 1
- 3
modules/buzzer/__init__.py Parādīt failu

@@ -41,10 +41,8 @@ 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)
cbpi.beep()


+ 3
- 5
modules/config/__init__.py Parādīt failu

@@ -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
modules/core/baseview.py Parādīt failu

@@ -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)



+ 2
- 2
modules/core/controller.py Parādīt failu

@@ -135,13 +135,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
- 23
modules/core/core.py Parādīt failu

@@ -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"):
@@ -73,12 +75,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 +159,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 +170,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 +223,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 +242,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 +267,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})
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, "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 +331,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 +353,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, "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 +453,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 +484,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
modules/core/db.py Parādīt failu

@@ -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


+ 47
- 0
modules/core/db_mirgrate.py Parādīt failu

@@ -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)





+ 11
- 7
modules/core/hardware.py Parādīt failu

@@ -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
- 7
modules/core/props.py Parādīt failu

@@ -3,36 +3,65 @@ 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.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.configurable = True
self.description = description

+ 5
- 5
modules/core/step.py Parādīt failu

@@ -44,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:
@@ -59,7 +59,7 @@ class Timer(object):
timer_end = Property.Number("TIMER_END", configurable=False)

def start_timer(self, timer):
print "START TIMER NEW"
if self.timer_end is not None:
return
self.timer_end = int(time.time()) + timer
@@ -100,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 "-------------"


+ 7
- 6
modules/fermenter/__init__.py Parādīt failu

@@ -63,18 +63,19 @@ 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

@route('/<int:id>/targettemp/<temp>', methods=['POST'])
@@ -282,7 +283,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 +296,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 +311,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()

+ 6
- 6
modules/kettle/__init__.py Parādīt failu

@@ -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


+ 0
- 1
modules/logs/endpoints.py Parādīt failu

@@ -105,5 +105,4 @@ def init(app):
:param app: the flask app
:return: None
"""
print "INITIALIZE LOG MODULE"
LogView.register(cbpi.app, route_base='/api/logs')

+ 6
- 8
modules/notification/__init__.py Parādīt failu

@@ -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')

+ 3
- 0
modules/recipe_import/__init__.py Parādīt failu

@@ -0,0 +1,3 @@
import beerxml
import kbh
import restapi

+ 110
- 0
modules/recipe_import/beerxml.py Parādīt failu

@@ -0,0 +1,110 @@
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)
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 cooking step
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.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 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')

+ 109
- 0
modules/recipe_import/kbh.py Parādīt failu

@@ -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')

+ 62
- 0
modules/recipe_import/restapi.py Parādīt failu

@@ -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')

+ 10
- 5
modules/sensors/__init__.py Parādīt failu

@@ -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


+ 1
- 1
modules/stats/__init__.py Parādīt failu

@@ -16,7 +16,7 @@ def getserial():

@cbpi.initalizer(order=9999)
def sendStats(cbpi):
print "INITIALIZE STATS"
try:
serial = getserial()



+ 24
- 15
modules/steps/__init__.py Parādīt failu

@@ -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)


+ 40
- 2
modules/system/endpoints.py Parādīt failu

@@ -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')

+ 3
- 2
modules/ui/endpoints.py Parādīt failu

@@ -5,11 +5,12 @@ react = Blueprint('react', __name__, template_folder='templates', static_folder=

@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")



+ 6006
- 0
modules/ui/package-lock.json
Failā izmaiņas netiks attēlotas, jo tās ir par lielu
Parādīt failu


+ 16
- 0
modules/ui/src/App.css Parādīt failu

@@ -0,0 +1,16 @@
.App {

}

.container {
padding-right: 5px;
padding-left: 5px;
margin-right: auto;
margin-left: auto;
}

.container-fluid {
padding-right: 5px;
padding-left: 5px;

}

+ 5
- 0
modules/ui/src/index.css Parādīt failu

@@ -0,0 +1,5 @@
body {
margin: 0;
padding: 0;
font-family: sans-serif;
}

+ 48
- 48
modules/ui/static/bundle.js
Failā izmaiņas netiks attēlotas, jo tās ir par lielu
Parādīt failu


+ 7
- 1
modules/ui/static/index.html Parādīt failu

@@ -13,6 +13,8 @@
<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;
@@ -50,11 +52,15 @@



/* Einschränken des sichtbaren Ausschnitts */



</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.


+ 1
- 2
requirements.txt Parādīt failu

@@ -11,5 +11,4 @@ requests==2.11.0
Werkzeug==0.11.10
httplib2==0.9.2
flask-classy==0.6.10
GitPython==2.1.3
GitPython==2.1.3

+ 18
- 0
update/1_schema_info.sql Parādīt failu

@@ -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);




+ 1
- 0
update/2_more_buzzer.sql Parādīt failu

@@ -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
update/empty Parādīt failu


Notiek ielāde…
Atcelt
Saglabāt