| @@ -8,3 +8,7 @@ craftbeerpi.db | |||||
| modules/plugins/* | modules/plugins/* | ||||
| !modules/plugins/__init__.py | !modules/plugins/__init__.py | ||||
| *.pyc | *.pyc | ||||
| *.js | |||||
| modules/ui/package.json | |||||
| modules/ui/.babelrc | |||||
| @@ -18,6 +18,12 @@ Type <code>cd craftbeerpi3</code> to navigate into the craftbeerpi folder. | |||||
| Type <code>sudo ./install.sh</code> | 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 | ## ATTENTION | ||||
| CraftBeerPi 3.0 is a complete rewrite. Server as well as user interface. I recommend to use a second SD card for testing. | CraftBeerPi 3.0 is a complete rewrite. Server as well as user interface. I recommend to use a second SD card for testing. | ||||
| @@ -1,4 +1,4 @@ | |||||
| CREATE TABLE kettle | |||||
| CREATE TABLE IF NOT EXISTS kettle | |||||
| ( | ( | ||||
| id INTEGER PRIMARY KEY NOT NULL, | id INTEGER PRIMARY KEY NOT NULL, | ||||
| name VARCHAR(80), | name VARCHAR(80), | ||||
| @@ -13,7 +13,7 @@ CREATE TABLE kettle | |||||
| diameter INTEGER | diameter INTEGER | ||||
| ); | ); | ||||
| CREATE TABLE step | |||||
| CREATE TABLE IF NOT EXISTS step | |||||
| ( | ( | ||||
| id INTEGER PRIMARY KEY NOT NULL, | id INTEGER PRIMARY KEY NOT NULL, | ||||
| "order" INTEGER, | "order" INTEGER, | ||||
| @@ -27,7 +27,7 @@ CREATE TABLE step | |||||
| kettleid INTEGER | kettleid INTEGER | ||||
| ); | ); | ||||
| CREATE TABLE sensor | |||||
| CREATE TABLE IF NOT EXISTS sensor | |||||
| ( | ( | ||||
| id INTEGER PRIMARY KEY NOT NULL, | id INTEGER PRIMARY KEY NOT NULL, | ||||
| type VARCHAR(100), | type VARCHAR(100), | ||||
| @@ -36,7 +36,7 @@ CREATE TABLE sensor | |||||
| hide BOOLEAN | hide BOOLEAN | ||||
| ); | ); | ||||
| CREATE TABLE fermenter_step | |||||
| CREATE TABLE IF NOT EXISTS fermenter_step | |||||
| ( | ( | ||||
| id INTEGER PRIMARY KEY NOT NULL, | id INTEGER PRIMARY KEY NOT NULL, | ||||
| name VARCHAR(80), | name VARCHAR(80), | ||||
| @@ -54,7 +54,7 @@ CREATE TABLE fermenter_step | |||||
| FOREIGN KEY (fermenter_id) REFERENCES fermenter (id) | FOREIGN KEY (fermenter_id) REFERENCES fermenter (id) | ||||
| ); | ); | ||||
| CREATE TABLE fermenter | |||||
| CREATE TABLE IF NOT EXISTS fermenter | |||||
| ( | ( | ||||
| id INTEGER PRIMARY KEY NOT NULL, | id INTEGER PRIMARY KEY NOT NULL, | ||||
| name VARCHAR(80), | name VARCHAR(80), | ||||
| @@ -69,7 +69,7 @@ CREATE TABLE fermenter | |||||
| target_temp INTEGER | target_temp INTEGER | ||||
| ); | ); | ||||
| CREATE TABLE config | |||||
| CREATE TABLE IF NOT EXISTS config | |||||
| ( | ( | ||||
| name VARCHAR(50) PRIMARY KEY NOT NULL, | name VARCHAR(50) PRIMARY KEY NOT NULL, | ||||
| value VARCHAR(255), | 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, | id INTEGER PRIMARY KEY NOT NULL, | ||||
| name VARCHAR(50), | name VARCHAR(50), | ||||
| type VARCHAR(100), | type VARCHAR(100), | ||||
| config VARCHAR(500), | config VARCHAR(500), | ||||
| hide BOOLEAN | hide BOOLEAN | ||||
| ); | |||||
| ); | |||||
| @@ -0,0 +1 @@ | |||||
| 3.0.2 | |||||
| @@ -35,6 +35,8 @@ import modules.system | |||||
| import modules.buzzer | import modules.buzzer | ||||
| import modules.stats | import modules.stats | ||||
| import modules.kettle | import modules.kettle | ||||
| import modules.recipe_import | |||||
| import modules.core.db_mirgrate | |||||
| from app_config import cbpi | from app_config import cbpi | ||||
| # Build the database: | # Build the database: | ||||
| # This will create the database file using SQLAlchemy | # 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("##########################################") | |||||
| @@ -17,10 +17,10 @@ class ActorView(BaseView): | |||||
| obj.state = 0 | obj.state = 0 | ||||
| obj.power = 100 | obj.power = 100 | ||||
| def post_post_callback(self, m): | |||||
| def _post_post_callback(self, m): | |||||
| self.api.init_actor(m.id) | self.api.init_actor(m.id) | ||||
| def post_put_callback(self, m): | |||||
| def _post_put_callback(self, m): | |||||
| self.api.init_actor(m.id) | 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) | t = self.api.socketio.start_background_task(target=self.toggleTimeJob, id=id, t=t) | ||||
| return ('', 204) | 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) | @cbpi.initalizer(order=1000) | ||||
| def init(cbpi): | def init(cbpi): | ||||
| print "INITIALIZE ACTOR MODULE" | |||||
| cbpi.app.logger.info("INITIALIZE ACTOR MODULE") | |||||
| ActorView.register(cbpi.app, route_base='/api/actor') | ActorView.register(cbpi.app, route_base='/api/actor') | ||||
| ActorView.init_cache() | ActorView.init_cache() | ||||
| cbpi.init_actors() | cbpi.init_actors() | ||||
| @@ -166,7 +166,9 @@ def update_addon(name): | |||||
| def loadCorePlugins(): | def loadCorePlugins(): | ||||
| for filename in os.listdir("./modules/base_plugins"): | 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 | continue | ||||
| try: | try: | ||||
| modules[filename] = import_module("modules.base_plugins.%s" % (filename)) | modules[filename] = import_module("modules.base_plugins.%s" % (filename)) | ||||
| @@ -178,7 +180,7 @@ def loadCorePlugins(): | |||||
| def loadPlugins(): | def loadPlugins(): | ||||
| for filename in os.listdir("./modules/plugins"): | 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 | continue | ||||
| try: | try: | ||||
| modules[filename] = import_module("modules.plugins.%s" % (filename)) | modules[filename] = import_module("modules.plugins.%s" % (filename)) | ||||
| @@ -188,11 +190,11 @@ def loadPlugins(): | |||||
| @cbpi.initalizer(order=1) | @cbpi.initalizer(order=1) | ||||
| def initPlugins(app): | def initPlugins(app): | ||||
| print "INITIALIZE CUSTOM PLUGINS" | |||||
| loadCorePlugins() | loadCorePlugins() | ||||
| loadPlugins() | loadPlugins() | ||||
| @cbpi.initalizer(order=2) | @cbpi.initalizer(order=2) | ||||
| def init(cbpi): | def init(cbpi): | ||||
| print "INITIALIZE ADDON MODULE" | |||||
| cbpi.app.register_blueprint(blueprint, url_prefix='/api/editor') | cbpi.app.register_blueprint(blueprint, url_prefix='/api/editor') | ||||
| @@ -15,7 +15,9 @@ from modules.core.db import DBModel | |||||
| app = Flask(__name__) | 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['SECRET_KEY'] = 'craftbeerpi' | ||||
| app.config['UPLOAD_FOLDER'] = './upload' | app.config['UPLOAD_FOLDER'] = './upload' | ||||
| @@ -45,7 +47,7 @@ class ComplexEncoder(json.JSONEncoder): | |||||
| return None | return None | ||||
| app.json_encoder = ComplexEncoder | app.json_encoder = ComplexEncoder | ||||
| socketio = SocketIO(app, json=json) | |||||
| socketio = SocketIO(app, json=json, logging=False) | |||||
| cbpi = CraftBeerPi(app, socketio) | cbpi = CraftBeerPi(app, socketio) | ||||
| app.logger.info("##########################################") | app.logger.info("##########################################") | ||||
| @@ -7,15 +7,16 @@ from modules.core.step import StepBase | |||||
| from modules import cbpi | from modules import cbpi | ||||
| @cbpi.step | @cbpi.step | ||||
| class MashStep(StepBase): | class MashStep(StepBase): | ||||
| ''' | ''' | ||||
| Just put the decorator @cbpi.step on top of a method | Just put the decorator @cbpi.step on top of a method | ||||
| ''' | ''' | ||||
| # Properties | # 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): | def init(self): | ||||
| ''' | ''' | ||||
| @@ -49,7 +50,7 @@ class MashStep(StepBase): | |||||
| ''' | ''' | ||||
| # Check if Target Temp is reached | # 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 | # Check if Timer is Running | ||||
| if self.is_timer_finished() is None: | if self.is_timer_finished() is None: | ||||
| self.start_timer(int(self.timer) * 60) | 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 | Just put the decorator @cbpi.step on top of a method | ||||
| ''' | ''' | ||||
| # Properties | # 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 | s = False | ||||
| @cbpi.action("Change Power") | |||||
| def change_power(self): | |||||
| self.actor_power(1, 50) | |||||
| def init(self): | def init(self): | ||||
| ''' | ''' | ||||
| Initialize Step. This method is called once at the beginning of the step | 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() | |||||
| @@ -10,21 +10,38 @@ from modules.core.props import Property | |||||
| @cbpi.sensor | @cbpi.sensor | ||||
| class DummyTempSensor(SensorActive): | 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): | 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" | return "°C" if self.get_config_parameter("unit", "C") == "C" else "°F" | ||||
| def stop(self): | def stop(self): | ||||
| pass | |||||
| SensorActive.stop(self) | |||||
| def execute(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) | self.data_received(self.temp) | ||||
| socketio.sleep(5) | |||||
| self.sleep(5) | |||||
| @classmethod | |||||
| def init_global(cls): | |||||
| ''' | |||||
| Called one at the startup for all sensors | |||||
| :return: | |||||
| ''' | |||||
| @@ -6,10 +6,10 @@ from modules.core.props import Property | |||||
| @cbpi.fermentation_controller | @cbpi.fermentation_controller | ||||
| class Hysteresis(FermenterController): | 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): | def stop(self): | ||||
| super(FermenterController, self).stop() | super(FermenterController, self).stop() | ||||
| @@ -23,16 +23,16 @@ class Hysteresis(FermenterController): | |||||
| target_temp = self.get_target_temp() | target_temp = self.get_target_temp() | ||||
| temp = self.get_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) | 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() | 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) | 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.cooler_off() | ||||
| self.sleep(1) | |||||
| self.sleep(1) | |||||
| @@ -18,7 +18,7 @@ except Exception as e: | |||||
| @cbpi.actor | @cbpi.actor | ||||
| class GPIOSimple(ActorBase): | 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): | def init(self): | ||||
| GPIO.setup(int(self.gpio), GPIO.OUT) | GPIO.setup(int(self.gpio), GPIO.OUT) | ||||
| @@ -35,7 +35,7 @@ class GPIOSimple(ActorBase): | |||||
| @cbpi.actor | @cbpi.actor | ||||
| class GPIOPWM(ActorBase): | 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) | duty_cylce = Property.Number("Duty Cycle", configurable=True) | ||||
| p = None | p = None | ||||
| @@ -57,6 +57,11 @@ class GPIOPWM(ActorBase): | |||||
| self.p.start(int(self.power)) | self.p.start(int(self.power)) | ||||
| def set_power(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: | if power is not None: | ||||
| self.power = int(power) | self.power = int(power) | ||||
| self.p.ChangeDutyCycle(self.power) | self.p.ChangeDutyCycle(self.power) | ||||
| @@ -69,7 +74,7 @@ class GPIOPWM(ActorBase): | |||||
| @cbpi.actor | @cbpi.actor | ||||
| class RelayBoard(ActorBase): | 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): | def init(self): | ||||
| GPIO.setup(int(self.gpio), GPIO.OUT) | GPIO.setup(int(self.gpio), GPIO.OUT) | ||||
| @@ -86,8 +91,17 @@ class RelayBoard(ActorBase): | |||||
| @cbpi.actor | @cbpi.actor | ||||
| class Dummy(ActorBase): | class Dummy(ActorBase): | ||||
| def on(self, power=100): | def on(self, power=100): | ||||
| ''' | |||||
| Code to switch on the actor | |||||
| :param power: int value between 0 - 100 | |||||
| :return: | |||||
| ''' | |||||
| print "ON" | print "ON" | ||||
| def off(self): | def off(self): | ||||
| print "OFF" | print "OFF" | ||||
| @@ -8,8 +8,8 @@ class Hysteresis(KettleController): | |||||
| # Custom Properties | # 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): | def stop(self): | ||||
| ''' | ''' | ||||
| @@ -30,11 +30,9 @@ class Hysteresis(KettleController): | |||||
| ''' | ''' | ||||
| while self.is_running(): | 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) | 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() | self.heater_off() | ||||
| else: | else: | ||||
| self.heater_off() | self.heater_off() | ||||
| @@ -66,7 +66,8 @@ class myThread (threading.Thread): | |||||
| @cbpi.sensor | @cbpi.sensor | ||||
| class ONE_WIRE_SENSOR(SensorPassive): | 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): | def init(self): | ||||
| @@ -86,10 +87,14 @@ class ONE_WIRE_SENSOR(SensorPassive): | |||||
| def read(self): | def read(self): | ||||
| if self.get_config_parameter("unit", "C") == "C": | 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: | 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 | @classmethod | ||||
| def init_global(self): | def init_global(self): | ||||
| try: | try: | ||||
| @@ -108,5 +113,5 @@ def set_temp(t): | |||||
| @cbpi.initalizer() | @cbpi.initalizer() | ||||
| def init(cbpi): | def init(cbpi): | ||||
| cbpi.app.logger.info("INITIALIZE ONE WIRE MODULE") | |||||
| cbpi.app.register_blueprint(blueprint, url_prefix='/api/one_wire') | cbpi.app.register_blueprint(blueprint, url_prefix='/api/one_wire') | ||||
| @@ -41,10 +41,8 @@ class Buzzer(object): | |||||
| start_new_thread(play, (self.sound,)) | start_new_thread(play, (self.sound,)) | ||||
| @cbpi.initalizer(order=0) | |||||
| @cbpi.initalizer(order=1) | |||||
| def init(cbpi): | def init(cbpi): | ||||
| print "INIT BUZZER" | |||||
| cbpi.app.logger.info("INIT BUZZER") | |||||
| gpio = cbpi.get_config_parameter("buzzer", 16) | gpio = cbpi.get_config_parameter("buzzer", 16) | ||||
| cbpi.buzzer = Buzzer(gpio) | cbpi.buzzer = Buzzer(gpio) | ||||
| cbpi.beep() | cbpi.beep() | ||||
| @@ -24,10 +24,9 @@ class ConfigView(BaseView): | |||||
| update_data = {"name": data["name"], "value": data["value"]} | update_data = {"name": data["name"], "value": data["value"]} | ||||
| if self.api.cache.get(self.cache_key) is not None: | 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) | 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__) | 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__) | return json.dumps(self.api.cache.get(self.cache_key)[name].__dict__) | ||||
| @route('/<id>', methods=["GET"]) | @route('/<id>', methods=["GET"]) | ||||
| @@ -51,9 +50,8 @@ class ConfigView(BaseView): | |||||
| cls.post_init_callback(value) | cls.post_init_callback(value) | ||||
| cls.api.cache[cls.cache_key][value.name] = value | cls.api.cache[cls.cache_key][value.name] = value | ||||
| @cbpi.initalizer(order=1) | |||||
| @cbpi.initalizer(order=0) | |||||
| def init(cbpi): | def init(cbpi): | ||||
| print "INITIALIZE CONFIG MODULE" | |||||
| ConfigView.register(cbpi.app, route_base='/api/config') | ConfigView.register(cbpi.app, route_base='/api/config') | ||||
| ConfigView.init_cache() | ConfigView.init_cache() | ||||
| @@ -9,7 +9,7 @@ class BaseView(FlaskView): | |||||
| cache_key = None | cache_key = None | ||||
| api = cbpi | api = cbpi | ||||
| @route('/<int:id>') | |||||
| @route('/<int:id>', methods=["GET"]) | |||||
| def getOne(self, id): | def getOne(self, id): | ||||
| if self.api.cache.get(self.cache_key) is not None: | if self.api.cache.get(self.cache_key) is not None: | ||||
| @@ -17,36 +17,36 @@ class BaseView(FlaskView): | |||||
| else: | else: | ||||
| return json.dumps(self.model.get_one(id)) | return json.dumps(self.model.get_one(id)) | ||||
| @route('/') | |||||
| @route('/', methods=["GET"]) | |||||
| def getAll(self): | def getAll(self): | ||||
| if self.api.cache.get(self.cache_key) is not None: | if self.api.cache.get(self.cache_key) is not None: | ||||
| return json.dumps(self.api.cache.get(self.cache_key)) | return json.dumps(self.api.cache.get(self.cache_key)) | ||||
| else: | else: | ||||
| return json.dumps(self.model.get_all()) | return json.dumps(self.model.get_all()) | ||||
| def pre_post_callback(self, data): | |||||
| def _pre_post_callback(self, data): | |||||
| pass | pass | ||||
| def post_post_callback(self, m): | |||||
| def _post_post_callback(self, m): | |||||
| pass | pass | ||||
| @route('/', methods=["POST"]) | @route('/', methods=["POST"]) | ||||
| def post(self): | def post(self): | ||||
| data = request.json | data = request.json | ||||
| self.pre_post_callback(data) | |||||
| self._pre_post_callback(data) | |||||
| m = self.model.insert(**data) | m = self.model.insert(**data) | ||||
| if self.api.cache.get(self.cache_key) is not None: | if self.api.cache.get(self.cache_key) is not None: | ||||
| self.api.cache.get(self.cache_key)[m.id] = m | self.api.cache.get(self.cache_key)[m.id] = m | ||||
| self.post_post_callback(m) | |||||
| self._post_post_callback(m) | |||||
| return json.dumps(m) | return json.dumps(m) | ||||
| def pre_put_callback(self, m): | |||||
| def _pre_put_callback(self, m): | |||||
| pass | pass | ||||
| def post_put_callback(self, m): | |||||
| def _post_put_callback(self, m): | |||||
| pass | pass | ||||
| @@ -59,32 +59,32 @@ class BaseView(FlaskView): | |||||
| except: | except: | ||||
| pass | pass | ||||
| if self.api.cache.get(self.cache_key) is not None: | 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) | self.api.cache.get(self.cache_key)[id].__dict__.update(**data) | ||||
| m = self.model.update(**self.api.cache.get(self.cache_key)[id].__dict__) | 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]) | return json.dumps(self.api.cache.get(self.cache_key)[id]) | ||||
| else: | else: | ||||
| m = self.model.update(**data) | m = self.model.update(**data) | ||||
| self.post_put_callback(m) | |||||
| self._post_put_callback(m) | |||||
| return json.dumps(m) | return json.dumps(m) | ||||
| def pre_delete_callback(self, m): | |||||
| def _pre_delete_callback(self, m): | |||||
| pass | pass | ||||
| def post_delete_callback(self, id): | |||||
| def _post_delete_callback(self, id): | |||||
| pass | pass | ||||
| @route('/<int:id>', methods=["DELETE"]) | @route('/<int:id>', methods=["DELETE"]) | ||||
| def delete(self, id): | def delete(self, id): | ||||
| if self.api.cache.get(self.cache_key) is not None: | 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] | del self.api.cache.get(self.cache_key)[id] | ||||
| m = self.model.delete(id) | m = self.model.delete(id) | ||||
| def post_delete_callback(self, id): | |||||
| def _post_delete_callback(self, id): | |||||
| pass | pass | ||||
| return ('',204) | return ('',204) | ||||
| @@ -135,13 +135,13 @@ class FermenterController(ControllerBase, ActorController, SensorController): | |||||
| @cbpi.try_catch(None) | @cbpi.try_catch(None) | ||||
| def cooler_on(self, power=100): | def cooler_on(self, power=100): | ||||
| f = self.api.cache.get("fermenter").get(self.fermenter_id) | 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)) | self.actor_on(power, int(f.cooler)) | ||||
| @cbpi.try_catch(None) | @cbpi.try_catch(None) | ||||
| def cooler_off(self): | def cooler_off(self): | ||||
| f = self.api.cache.get("fermenter").get(self.fermenter_id) | 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)) | self.actor_off(int(f.cooler)) | ||||
| @cbpi.try_catch(None) | @cbpi.try_catch(None) | ||||
| @@ -1,3 +1,4 @@ | |||||
| import inspect | |||||
| import pprint | import pprint | ||||
| import sqlite3 | import sqlite3 | ||||
| @@ -28,6 +29,7 @@ class ActorAPI(object): | |||||
| self.app.logger.info("Init Actors") | self.app.logger.info("Init Actors") | ||||
| t = self.cache.get("actor_types") | t = self.cache.get("actor_types") | ||||
| for key, value in t.iteritems(): | for key, value in t.iteritems(): | ||||
| value.get("class").api = self | |||||
| value.get("class").init_global() | value.get("class").init_global() | ||||
| for key in self.cache.get("actors"): | for key in self.cache.get("actors"): | ||||
| @@ -73,12 +75,8 @@ class ActorAPI(object): | |||||
| if actor.state == 0: | if actor.state == 0: | ||||
| return | return | ||||
| actor.instance.off() | actor.instance.off() | ||||
| actor.state = 0 | actor.state = 0 | ||||
| self.emit("SWITCH_ACTOR", actor) | self.emit("SWITCH_ACTOR", actor) | ||||
| class SensorAPI(object): | class SensorAPI(object): | ||||
| @@ -161,9 +159,8 @@ class SensorAPI(object): | |||||
| def log_action(self, text): | def log_action(self, text): | ||||
| filename = "./logs/action.log" | filename = "./logs/action.log" | ||||
| formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime()) | formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime()) | ||||
| with open(filename, "a") as file: | with open(filename, "a") as file: | ||||
| text = text.encode("utf-8") | |||||
| file.write("%s,%s\n" % (formatted_time, text)) | file.write("%s,%s\n" % (formatted_time, text)) | ||||
| def shutdown_sensor(self, id): | def shutdown_sensor(self, id): | ||||
| @@ -173,7 +170,7 @@ class SensorAPI(object): | |||||
| def get_sensor_value(self, id): | def get_sensor_value(self, id): | ||||
| try: | try: | ||||
| id = int(id) | 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: | except Exception as e: | ||||
| return None | return None | ||||
| @@ -226,12 +223,9 @@ class CraftBeerPi(ActorAPI, SensorAPI): | |||||
| def notify(self, headline, message, type="success", timeout=5000): | def notify(self, headline, message, type="success", timeout=5000): | ||||
| self.beep() | self.beep() | ||||
| msg = {"id": str(uuid.uuid1()), "type": type, "headline": headline, "message": message, "timeout": timeout} | 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): | def beep(self): | ||||
| if self.buzzer is not None: | if self.buzzer is not None: | ||||
| self.buzzer.beep() | self.buzzer.beep() | ||||
| @@ -248,6 +242,15 @@ class CraftBeerPi(ActorAPI, SensorAPI): | |||||
| else: | else: | ||||
| return cfg.value | 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): | def add_config_parameter(self, name, value, type, description, options=None): | ||||
| from modules.config import Config | from modules.config import Config | ||||
| with self.app.app_context(): | with self.app.app_context(): | ||||
| @@ -264,28 +267,56 @@ class CraftBeerPi(ActorAPI, SensorAPI): | |||||
| # helper method for parsing props | # helper method for parsing props | ||||
| def __parseProps(self, key, cls): | def __parseProps(self, key, cls): | ||||
| name = cls.__name__ | name = cls.__name__ | ||||
| self.cache[key][name] = {"name": name, "class": cls, "properties": []} | |||||
| self.cache[key][name] = {"name": name, "class": cls, "properties": [], "actions": []} | |||||
| tmpObj = cls() | tmpObj = cls() | ||||
| members = [attr for attr in dir(tmpObj) if not callable(getattr(tmpObj, attr)) and not attr.startswith("__")] | members = [attr for attr in dir(tmpObj) if not callable(getattr(tmpObj, attr)) and not attr.startswith("__")] | ||||
| for m in members: | for m in members: | ||||
| if isinstance(tmpObj.__getattribute__(m), Property.Number): | if isinstance(tmpObj.__getattribute__(m), Property.Number): | ||||
| t = tmpObj.__getattribute__(m) | t = tmpObj.__getattribute__(m) | ||||
| self.cache[key][name]["properties"].append( | 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): | elif isinstance(tmpObj.__getattribute__(m), Property.Text): | ||||
| t = tmpObj.__getattribute__(m) | t = tmpObj.__getattribute__(m) | ||||
| self.cache[key][name]["properties"].append( | 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): | elif isinstance(tmpObj.__getattribute__(m), Property.Select): | ||||
| t = tmpObj.__getattribute__(m) | t = tmpObj.__getattribute__(m) | ||||
| self.cache[key][name]["properties"].append( | 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 | return cls | ||||
| def actor(self, cls): | def actor(self, cls): | ||||
| return self.__parseProps("actor_types", 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): | def sensor(self, cls): | ||||
| return self.__parseProps("sensor_types", cls) | return self.__parseProps("sensor_types", cls) | ||||
| @@ -300,6 +331,8 @@ class CraftBeerPi(ActorAPI, SensorAPI): | |||||
| def get_fermentation_controller(self, name): | def get_fermentation_controller(self, name): | ||||
| return self.cache["fermentation_controller_types"].get(name) | return self.cache["fermentation_controller_types"].get(name) | ||||
| # Step action | # Step action | ||||
| def action(self,label): | def action(self,label): | ||||
| def real_decorator(func): | def real_decorator(func): | ||||
| @@ -320,22 +353,22 @@ class CraftBeerPi(ActorAPI, SensorAPI): | |||||
| for m in members: | for m in members: | ||||
| if isinstance(tmpObj.__getattribute__(m), StepProperty.Number): | if isinstance(tmpObj.__getattribute__(m), StepProperty.Number): | ||||
| t = tmpObj.__getattribute__(m) | 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): | elif isinstance(tmpObj.__getattribute__(m), StepProperty.Text): | ||||
| t = tmpObj.__getattribute__(m) | 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): | elif isinstance(tmpObj.__getattribute__(m), StepProperty.Select): | ||||
| t = tmpObj.__getattribute__(m) | 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): | elif isinstance(tmpObj.__getattribute__(m), StepProperty.Actor): | ||||
| t = tmpObj.__getattribute__(m) | 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): | elif isinstance(tmpObj.__getattribute__(m), StepProperty.Sensor): | ||||
| t = tmpObj.__getattribute__(m) | 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): | elif isinstance(tmpObj.__getattribute__(m), StepProperty.Kettle): | ||||
| t = tmpObj.__getattribute__(m) | 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(): | for name, method in cls.__dict__.iteritems(): | ||||
| if hasattr(method, "action"): | if hasattr(method, "action"): | ||||
| @@ -420,7 +453,7 @@ class CraftBeerPi(ActorAPI, SensorAPI): | |||||
| self.app.logger.info("Invoke Init") | self.app.logger.info("Invoke Init") | ||||
| self.cache["init"] = sorted(self.cache["init"], key=lambda k: k['order']) | self.cache["init"] = sorted(self.cache["init"], key=lambda k: k['order']) | ||||
| for i in self.cache.get("init"): | 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) | i.get("function")(self) | ||||
| @@ -451,7 +484,7 @@ class CraftBeerPi(ActorAPI, SensorAPI): | |||||
| def job(interval, method): | def job(interval, method): | ||||
| while True: | while True: | ||||
| try: | try: | ||||
| method() | |||||
| method(self) | |||||
| except Exception as e: | except Exception as e: | ||||
| self.app.logger.error("Exception" + method.__name__ + ": " + str(e)) | self.app.logger.error("Exception" + method.__name__ + ": " + str(e)) | ||||
| self.socketio.sleep(interval) | self.socketio.sleep(interval) | ||||
| @@ -42,13 +42,15 @@ class DBModel(object): | |||||
| def get_all(cls): | def get_all(cls): | ||||
| cur = get_db().cursor() | cur = get_db().cursor() | ||||
| if cls.__order_by__ is not None: | 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: | else: | ||||
| cur.execute("SELECT * FROM %s" % cls.__table_name__) | cur.execute("SELECT * FROM %s" % cls.__table_name__) | ||||
| if cls.__as_array__ is True: | if cls.__as_array__ is True: | ||||
| result = [] | result = [] | ||||
| for r in cur.fetchall(): | for r in cur.fetchall(): | ||||
| result.append( cls(r)) | result.append( cls(r)) | ||||
| else: | else: | ||||
| result = {} | result = {} | ||||
| @@ -104,7 +106,7 @@ class DBModel(object): | |||||
| else: | else: | ||||
| data = data + (kwargs.get(f),) | data = data + (kwargs.get(f),) | ||||
| print query, data | |||||
| cur.execute(query, data) | cur.execute(query, data) | ||||
| get_db().commit() | get_db().commit() | ||||
| i = cur.lastrowid | i = cur.lastrowid | ||||
| @@ -0,0 +1,47 @@ | |||||
| import sqlite3 | |||||
| import os | |||||
| from modules import cbpi | |||||
| from db import get_db | |||||
| def execute_file(curernt_version, data): | |||||
| if curernt_version >= data["version"]: | |||||
| cbpi.app.logger.info("SKIP DB FILE: %s" % data["file"]) | |||||
| return | |||||
| try: | |||||
| with sqlite3.connect("craftbeerpi.db") as conn: | |||||
| with open('./update/%s' % data["file"], 'r') as f: | |||||
| d = f.read() | |||||
| sqlCommands = d.split(";") | |||||
| cur = conn.cursor() | |||||
| for s in sqlCommands: | |||||
| cur.execute(s) | |||||
| cur.execute("INSERT INTO schema_info (version,filename) values (?,?)", (data["version"], data["file"])) | |||||
| conn.commit() | |||||
| except sqlite3.OperationalError as err: | |||||
| print "EXCEPT" | |||||
| print err | |||||
| @cbpi.initalizer(order=-9999) | |||||
| def init(app=None): | |||||
| with cbpi.app.app_context(): | |||||
| conn = get_db() | |||||
| cur = conn.cursor() | |||||
| current_version = None | |||||
| try: | |||||
| cur.execute("SELECT max(version) as m FROM schema_info") | |||||
| m = cur.fetchone() | |||||
| current_version = m["m"] | |||||
| except: | |||||
| pass | |||||
| result = [] | |||||
| for filename in os.listdir("./update"): | |||||
| if filename.endswith(".sql"): | |||||
| d = {"version": int(filename[:filename.index('_')]), "file": filename} | |||||
| result.append(d) | |||||
| execute_file(current_version, d) | |||||
| @@ -5,16 +5,19 @@ class Base(object): | |||||
| @classmethod | @classmethod | ||||
| def init_global(cls): | def init_global(cls): | ||||
| print "GLOBAL ACTOR INIT" | |||||
| pass | |||||
| def get_config_parameter(self, key, default_value): | def get_config_parameter(self, key, default_value): | ||||
| return self.api.get_config_parameter(key, default_value) | return self.api.get_config_parameter(key, default_value) | ||||
| def sleep(self, seconds): | |||||
| self.api.socketio.sleep(seconds) | |||||
| def init(self): | def init(self): | ||||
| print "INIT BASE" | |||||
| pass | |||||
| def stop(self): | def stop(self): | ||||
| print "STOP HARDWARE" | |||||
| pass | |||||
| def update(self, **kwds): | def update(self, **kwds): | ||||
| pass | pass | ||||
| @@ -67,15 +70,16 @@ class SensorActive(SensorBase): | |||||
| __running = False | __running = False | ||||
| def is_running(self): | def is_running(self): | ||||
| return self.__running | return self.__running | ||||
| def init(self): | def init(self): | ||||
| self.__running = True | self.__running = True | ||||
| print "INIT ACTIVE SENSOR" | |||||
| def stop(self): | def stop(self): | ||||
| self.__running = False | self.__running = False | ||||
| def execute(self): | def execute(self): | ||||
| pass | pass | ||||
| @@ -95,10 +99,10 @@ class ActorBase(Base): | |||||
| return 1 | return 1 | ||||
| def set_power(self, power): | def set_power(self, power): | ||||
| print "SET POWER TO %s" % power | |||||
| pass | |||||
| def on(self, power=0): | def on(self, power=0): | ||||
| print "ON" | |||||
| pass | |||||
| def off(self): | def off(self): | ||||
| print "OFF" | |||||
| pass | |||||
| @@ -3,36 +3,65 @@ class PropertyType(object): | |||||
| class Property(object): | class Property(object): | ||||
| class Select(PropertyType): | class Select(PropertyType): | ||||
| def __init__(self, label, options): | |||||
| def __init__(self, label, options, description=""): | |||||
| PropertyType.__init__(self) | PropertyType.__init__(self) | ||||
| self.label = label | self.label = label | ||||
| self.options = options | self.options = options | ||||
| self.description = description | |||||
| class Number(PropertyType): | 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) | PropertyType.__init__(self) | ||||
| self.label = label | self.label = label | ||||
| self.configurable = configurable | self.configurable = configurable | ||||
| self.default_value = default_value | |||||
| self.description = description | |||||
| class Text(PropertyType): | class Text(PropertyType): | ||||
| def __init__(self, label, configurable=False, default_value=""): | |||||
| def __init__(self, label, configurable=False, default_value="", description=""): | |||||
| PropertyType.__init__(self) | PropertyType.__init__(self) | ||||
| self.label = label | self.label = label | ||||
| self.configurable = configurable | 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 StepProperty(Property): | ||||
| class Actor(PropertyType): | class Actor(PropertyType): | ||||
| def __init__(self, label): | |||||
| def __init__(self, label, description=""): | |||||
| PropertyType.__init__(self) | PropertyType.__init__(self) | ||||
| self.label = label | self.label = label | ||||
| self.configurable = True | self.configurable = True | ||||
| self.description = description | |||||
| class Sensor(PropertyType): | class Sensor(PropertyType): | ||||
| def __init__(self, label): | |||||
| def __init__(self, label, description=""): | |||||
| PropertyType.__init__(self) | PropertyType.__init__(self) | ||||
| self.label = label | self.label = label | ||||
| self.configurable = True | self.configurable = True | ||||
| self.description = description | |||||
| class Kettle(PropertyType): | class Kettle(PropertyType): | ||||
| def __init__(self, label): | |||||
| def __init__(self, label, description=""): | |||||
| PropertyType.__init__(self) | PropertyType.__init__(self) | ||||
| self.label = label | self.label = label | ||||
| self.configurable = True | |||||
| self.configurable = True | |||||
| self.description = description | |||||
| @@ -44,7 +44,7 @@ class KettleAPI(NotificationAPI): | |||||
| return self.api.cache.get("kettle").get(id).target_temp | return self.api.cache.get("kettle").get(id).target_temp | ||||
| def set_target_temp(self, temp, id=None): | def set_target_temp(self, temp, id=None): | ||||
| temp = int(temp) | |||||
| temp = float(temp) | |||||
| try: | try: | ||||
| if id is None: | if id is None: | ||||
| @@ -59,7 +59,7 @@ class Timer(object): | |||||
| timer_end = Property.Number("TIMER_END", configurable=False) | timer_end = Property.Number("TIMER_END", configurable=False) | ||||
| def start_timer(self, timer): | def start_timer(self, timer): | ||||
| print "START TIMER NEW" | |||||
| if self.timer_end is not None: | if self.timer_end is not None: | ||||
| return | return | ||||
| self.timer_end = int(time.time()) + timer | self.timer_end = int(time.time()) + timer | ||||
| @@ -100,13 +100,13 @@ class StepBase(Timer, ActorAPI, SensorAPI, KettleAPI): | |||||
| self.n = True | self.n = True | ||||
| def init(self): | def init(self): | ||||
| print "INIT STEP" | |||||
| pass | |||||
| def finish(self): | def finish(self): | ||||
| print "FINSIH STEP" | |||||
| pass | |||||
| def reset(self): | def reset(self): | ||||
| print "REST STEP" | |||||
| pass | |||||
| def execute(self): | def execute(self): | ||||
| print "-------------" | print "-------------" | ||||
| @@ -63,18 +63,19 @@ class FermenterView(BaseView): | |||||
| model = Fermenter | model = Fermenter | ||||
| cache_key = "fermenter" | cache_key = "fermenter" | ||||
| def post_post_callback(self, m): | |||||
| def _post_post_callback(self, m): | |||||
| m.state = False | m.state = False | ||||
| m.steps = [] | m.steps = [] | ||||
| def pre_put_callback(self, m): | |||||
| def _pre_put_callback(self, m): | |||||
| m.state = False | m.state = False | ||||
| try: | try: | ||||
| m.instance.stop() | m.instance.stop() | ||||
| except: | except: | ||||
| pass | pass | ||||
| def post_put_callback(self, m): | |||||
| def _post_put_callback(self, m): | |||||
| m.state = False | m.state = False | ||||
| @route('/<int:id>/targettemp/<temp>', methods=['POST']) | @route('/<int:id>/targettemp/<temp>', methods=['POST']) | ||||
| @@ -282,7 +283,7 @@ class FermenterView(BaseView): | |||||
| @cbpi.backgroundtask(key="read_target_temps_fermenter", interval=5) | @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 | background process that reads all passive sensors in interval of 1 second | ||||
| :return: None | :return: None | ||||
| @@ -295,7 +296,7 @@ def read_target_temps(): | |||||
| instance = FermenterView() | instance = FermenterView() | ||||
| @cbpi.backgroundtask(key="fermentation_task", interval=1) | @cbpi.backgroundtask(key="fermentation_task", interval=1) | ||||
| def execute_fermentation_step(): | |||||
| def execute_fermentation_step(api): | |||||
| with cbpi.app.app_context(): | with cbpi.app.app_context(): | ||||
| instance.check_step() | instance.check_step() | ||||
| @@ -310,6 +311,6 @@ def init_active_steps(): | |||||
| @cbpi.initalizer(order=1) | @cbpi.initalizer(order=1) | ||||
| def init(cbpi): | def init(cbpi): | ||||
| print "INITIALIZE CONFIG MODULE" | |||||
| FermenterView.register(cbpi.app, route_base='/api/fermenter') | FermenterView.register(cbpi.app, route_base='/api/fermenter') | ||||
| FermenterView.init_cache() | FermenterView.init_cache() | ||||
| @@ -15,7 +15,7 @@ class Kettle2View(BaseView): | |||||
| cache_key = "kettle" | cache_key = "kettle" | ||||
| @classmethod | @classmethod | ||||
| def pre_post_callback(self, data): | |||||
| def _pre_post_callback(self, data): | |||||
| data["target_temp"] = 0 | data["target_temp"] = 0 | ||||
| @classmethod | @classmethod | ||||
| @@ -23,16 +23,16 @@ class Kettle2View(BaseView): | |||||
| obj.state = False | obj.state = False | ||||
| def post_post_callback(self, m): | |||||
| def _post_post_callback(self, m): | |||||
| m.state = False | m.state = False | ||||
| def pre_put_callback(self, m): | |||||
| def _pre_put_callback(self, m): | |||||
| try: | try: | ||||
| m.instance.stop() | m.instance.stop() | ||||
| except: | except: | ||||
| pass | pass | ||||
| def post_put_callback(self, m): | |||||
| def _post_put_callback(self, m): | |||||
| m.state = False | m.state = False | ||||
| @route('/<int:id>/targettemp/<temp>', methods=['POST']) | @route('/<int:id>/targettemp/<temp>', methods=['POST']) | ||||
| @@ -76,11 +76,11 @@ def set_target_temp(id, temp): | |||||
| :param temp: target temp to set | :param temp: target temp to set | ||||
| :return: None | :return: None | ||||
| ''' | ''' | ||||
| print "GOT EVENT %s %s" % (id, temp) | |||||
| Kettle2View().postTargetTemp(id,temp) | Kettle2View().postTargetTemp(id,temp) | ||||
| @cbpi.backgroundtask(key="read_target_temps", interval=5) | @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 | background process that reads all passive sensors in interval of 1 second | ||||
| :return: None | :return: None | ||||
| @@ -105,5 +105,4 @@ def init(app): | |||||
| :param app: the flask app | :param app: the flask app | ||||
| :return: None | :return: None | ||||
| """ | """ | ||||
| print "INITIALIZE LOG MODULE" | |||||
| LogView.register(cbpi.app, route_base='/api/logs') | LogView.register(cbpi.app, route_base='/api/logs') | ||||
| @@ -32,10 +32,9 @@ def messageEvent(message, **kwargs): | |||||
| :param kwargs: other parameter | :param kwargs: other parameter | ||||
| :return: None | :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) | @cbpi.initalizer(order=2) | ||||
| def init(cbpi): | def init(cbpi): | ||||
| @@ -44,9 +43,8 @@ def init(cbpi): | |||||
| :param app: the flask app | :param app: the flask app | ||||
| :return: None | :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') | NotificationView.register(cbpi.app, route_base='/api/notification') | ||||
| @@ -0,0 +1,3 @@ | |||||
| import beerxml | |||||
| import kbh | |||||
| import restapi | |||||
| @@ -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') | |||||
| @@ -0,0 +1,109 @@ | |||||
| from flask import json, request | |||||
| from flask_classy import FlaskView, route | |||||
| from git import Repo, Git | |||||
| import sqlite3 | |||||
| from modules.app_config import cbpi | |||||
| from werkzeug.utils import secure_filename | |||||
| import pprint | |||||
| import time | |||||
| import os | |||||
| from modules.steps import Step, StepView | |||||
| class KBH(FlaskView): | |||||
| @route('/', methods=['GET']) | |||||
| def get(self): | |||||
| conn = None | |||||
| try: | |||||
| if not os.path.exists(self.api.app.config['UPLOAD_FOLDER'] + '/kbh.db'): | |||||
| self.api.notify(headline="File Not Found", message="Please upload a Kleiner Brauhelfer Database", type="danger") | |||||
| return ('', 404) | |||||
| conn = sqlite3.connect(self.api.app.config['UPLOAD_FOLDER'] + '/kbh.db') | |||||
| c = conn.cursor() | |||||
| c.execute('SELECT ID, Sudname, BierWurdeGebraut FROM Sud') | |||||
| data = c.fetchall() | |||||
| result = [] | |||||
| for row in data: | |||||
| result.append({"id": row[0], "name": row[1], "brewed": row[2]}) | |||||
| return json.dumps(result) | |||||
| except Exception as e: | |||||
| print e | |||||
| self.api.notify(headline="Failed to load KHB database", message="ERROR", type="danger") | |||||
| return ('', 500) | |||||
| finally: | |||||
| if conn: | |||||
| conn.close() | |||||
| def allowed_file(self, filename): | |||||
| return '.' in filename and filename.rsplit('.', 1)[1] in set(['sqlite']) | |||||
| @route('/upload', methods=['POST']) | |||||
| def upload_file(self): | |||||
| try: | |||||
| if request.method == 'POST': | |||||
| file = request.files['file'] | |||||
| if file and self.allowed_file(file.filename): | |||||
| filename = secure_filename(file.filename) | |||||
| file.save(os.path.join(self.api.app.config['UPLOAD_FOLDER'], "kbh.db")) | |||||
| self.api.notify(headline="Upload Successful", message="The Kleiner Brauhelfer Database was uploaded succesfully") | |||||
| return ('', 204) | |||||
| return ('', 404) | |||||
| except Exception as e: | |||||
| self.api.notify(headline="Upload Failed", message="Failed to upload Kleiner Brauhelfer", type="danger") | |||||
| return ('', 500) | |||||
| @route('/<int:id>', methods=['POST']) | |||||
| def load(self, id): | |||||
| mashstep_type = cbpi.get_config_parameter("step_mash", "MashStep") | |||||
| mashinstep_type = cbpi.get_config_parameter("step_mashin", "MashInStep") | |||||
| chilstep_type = cbpi.get_config_parameter("step_chil", "ChilStep") | |||||
| boilstep_type = cbpi.get_config_parameter("step_boil", "BoilStep") | |||||
| mash_kettle = cbpi.get_config_parameter("step_mash_kettle", None) | |||||
| boil_kettle = cbpi.get_config_parameter("step_boil_kettle", None) | |||||
| boil_temp = 100 if cbpi.get_config_parameter("unit", "C") == "C" else 212 | |||||
| # READ KBH DATABASE | |||||
| Step.delete_all() | |||||
| StepView().reset() | |||||
| conn = None | |||||
| try: | |||||
| conn = sqlite3.connect(self.api.app.config['UPLOAD_FOLDER'] + '/kbh.db') | |||||
| c = conn.cursor() | |||||
| c.execute('SELECT EinmaischenTemp, Sudname FROM Sud WHERE ID = ?', (id,)) | |||||
| row = c.fetchone() | |||||
| name = row[1] | |||||
| self.api.set_config_parameter("brew_name", name) | |||||
| Step.insert(**{"name": "MashIn", "type": mashinstep_type, "config": {"kettle": mash_kettle, "temp": row[0]}}) | |||||
| ### add rest step | |||||
| for row in c.execute('SELECT * FROM Rasten WHERE SudID = ?', (id,)): | |||||
| Step.insert(**{"name": row[5], "type": mashstep_type, "config": {"kettle": mash_kettle, "temp": row[3], "timer": row[4]}}) | |||||
| Step.insert(**{"name": "Chil", "type": chilstep_type, "config": {"timer": 15}}) | |||||
| ## Add cooking step | |||||
| c.execute('SELECT max(Zeit) FROM Hopfengaben WHERE SudID = ?', (id,)) | |||||
| row = c.fetchone() | |||||
| Step.insert(**{"name": "Boil", "type": boilstep_type, "config": {"kettle": boil_kettle, "temp": boil_temp, "timer": row[0]}}) | |||||
| ## Add Whirlpool step | |||||
| Step.insert(**{"name": "Whirlpool", "type": chilstep_type, "config": {"timer": 15}}) | |||||
| #setBrewName(name) | |||||
| self.api.emit("UPDATE_ALL_STEPS", Step.get_all()) | |||||
| self.api.notify(headline="Recipe %s loaded successfully" % name, message="") | |||||
| except Exception as e: | |||||
| self.api.notify(headline="Failed to load Recipe", message=e.message, type="danger") | |||||
| return ('', 500) | |||||
| finally: | |||||
| if conn: | |||||
| conn.close() | |||||
| return ('', 204) | |||||
| @cbpi.initalizer() | |||||
| def init(cbpi): | |||||
| KBH.api = cbpi | |||||
| KBH.register(cbpi.app, route_base='/api/kbh') | |||||
| @@ -0,0 +1,62 @@ | |||||
| from flask import json, request | |||||
| from flask_classy import FlaskView, route | |||||
| from git import Repo, Git | |||||
| import sqlite3 | |||||
| from modules.app_config import cbpi | |||||
| from werkzeug.utils import secure_filename | |||||
| import pprint | |||||
| import time | |||||
| import os | |||||
| from modules.steps import Step,StepView | |||||
| import xml.etree.ElementTree | |||||
| class RESTImport(FlaskView): | |||||
| @route('/', methods=['POST']) | |||||
| def load(self): | |||||
| try: | |||||
| data = request.json | |||||
| name = data.get("name", "No Name") | |||||
| self.api.set_config_parameter("brew_name", name) | |||||
| chilstep_type = cbpi.get_config_parameter("step_chil", "ChilStep") | |||||
| mashstep_type = cbpi.get_config_parameter("step_mash", "MashStep") | |||||
| mash_kettle = cbpi.get_config_parameter("step_mash_kettle", None) | |||||
| boilstep_type = cbpi.get_config_parameter("step_boil", "BoilStep") | |||||
| boil_kettle = cbpi.get_config_parameter("step_boil_kettle", None) | |||||
| boil_temp = 100 if cbpi.get_config_parameter("unit", "C") == "C" else 212 | |||||
| # READ KBH DATABASE | |||||
| Step.delete_all() | |||||
| StepView().reset() | |||||
| for step in data.get("steps"): | |||||
| if step.get("type", None) == "MASH": | |||||
| Step.insert(**{"name": step.get("name","Mash Step"), "type": mashstep_type, "config": {"kettle": mash_kettle, "temp": step.get("temp",0), "timer": step.get("timer",0)}}) | |||||
| elif step.get("type", None) == "CHIL": | |||||
| Step.insert(**{"name": step.get("name","Chil"), "type": chilstep_type, "config": {"timer": step.get("timer")}}) | |||||
| elif step.get("type", None) == "BOIL": | |||||
| Step.insert(**{"name": step.get("name", "Boil"), "type": boilstep_type, "config": {"kettle": boil_kettle, "timer": step.get("timer"), "temp": boil_temp}}) | |||||
| else: | |||||
| pass | |||||
| self.api.emit("UPDATE_ALL_STEPS", Step.get_all()) | |||||
| self.api.notify(headline="Recipe %s loaded successfully" % name, message="") | |||||
| except Exception as e: | |||||
| self.api.notify(headline="Failed to load Recipe", type="danger", message=str(e)) | |||||
| m = str(e.message) | |||||
| return (str(e), 500) | |||||
| return ('', 204) | |||||
| @cbpi.initalizer() | |||||
| def init(cbpi): | |||||
| RESTImport.api = cbpi | |||||
| RESTImport.register(cbpi.app, route_base='/api/recipe/import/v1') | |||||
| @@ -12,27 +12,32 @@ class SensorView(BaseView): | |||||
| model = Sensor | model = Sensor | ||||
| cache_key = "sensors" | 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) | cbpi.init_sensor(m.id) | ||||
| def post_put_callback(self, m): | |||||
| def _post_put_callback(self, m): | |||||
| cbpi.stop_sensor(m.id) | cbpi.stop_sensor(m.id) | ||||
| cbpi.init_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.stop_sensor(m.id) | ||||
| @cbpi.initalizer(order=1000) | @cbpi.initalizer(order=1000) | ||||
| def init(cbpi): | def init(cbpi): | ||||
| print "INITIALIZE SENSOR MODULE" | |||||
| SensorView.register(cbpi.app, route_base='/api/sensor') | SensorView.register(cbpi.app, route_base='/api/sensor') | ||||
| SensorView.init_cache() | SensorView.init_cache() | ||||
| cbpi.init_sensors() | cbpi.init_sensors() | ||||
| @cbpi.backgroundtask(key="read_passiv_sensor", interval=5) | @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 | background process that reads all passive sensors in interval of 1 second | ||||
| :return: None | :return: None | ||||
| @@ -16,7 +16,7 @@ def getserial(): | |||||
| @cbpi.initalizer(order=9999) | @cbpi.initalizer(order=9999) | ||||
| def sendStats(cbpi): | def sendStats(cbpi): | ||||
| print "INITIALIZE STATS" | |||||
| try: | try: | ||||
| serial = getserial() | serial = getserial() | ||||
| @@ -1,5 +1,5 @@ | |||||
| import time | import time | ||||
| from flask import json | |||||
| from flask import json, request | |||||
| from flask_classy import route | from flask_classy import route | ||||
| from modules import DBModel, cbpi, get_db | from modules import DBModel, cbpi, get_db | ||||
| @@ -21,9 +21,9 @@ class Step(DBModel): | |||||
| return r.get("order") | return r.get("order") | ||||
| @classmethod | @classmethod | ||||
| def get_by_state(cls, state): | |||||
| def get_by_state(cls, state, order=True): | |||||
| cur = get_db().cursor() | 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() | r = cur.fetchone() | ||||
| if r is not None: | if r is not None: | ||||
| return cls(r) | 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)) | cur.execute("UPDATE %s SET stepstate = ? WHERE id =?" % cls.__table_name__, (json.dumps(state),id)) | ||||
| get_db().commit() | 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): | class StepView(BaseView): | ||||
| model = Step | model = Step | ||||
| def pre_post_callback(self, data): | |||||
| def _pre_post_callback(self, data): | |||||
| order = self.model.get_max_order() | order = self.model.get_max_order() | ||||
| data["order"] = 1 if order is None else order + 1 | data["order"] = 1 if order is None else order + 1 | ||||
| data["state"] = "I" | 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"]) | @route('/', methods=["DELETE"]) | ||||
| def deleteAll(self): | def deleteAll(self): | ||||
| self.model.delete_all() | self.model.delete_all() | ||||
| @@ -69,16 +85,12 @@ class StepView(BaseView): | |||||
| @route('/action/<method>', methods=["POST"]) | @route('/action/<method>', methods=["POST"]) | ||||
| def action(self, method): | def action(self, method): | ||||
| cbpi.cache["active_step"].__getattribute__(method)() | cbpi.cache["active_step"].__getattribute__(method)() | ||||
| return ('', 204) | return ('', 204) | ||||
| @route('/reset', methods=["POST"]) | @route('/reset', methods=["POST"]) | ||||
| def reset(self): | def reset(self): | ||||
| self.model.reset_all_steps() | self.model.reset_all_steps() | ||||
| #db.session.commit() | |||||
| self.stop_step() | self.stop_step() | ||||
| cbpi.emit("UPDATE_ALL_STEPS", self.model.get_all()) | cbpi.emit("UPDATE_ALL_STEPS", self.model.get_all()) | ||||
| return ('', 204) | return ('', 204) | ||||
| @@ -115,7 +127,6 @@ class StepView(BaseView): | |||||
| return ('', 204) | return ('', 204) | ||||
| def init_step(self, step): | def init_step(self, step): | ||||
| cbpi.log_action("Start Step %s" % step.name) | cbpi.log_action("Start Step %s" % step.name) | ||||
| type_cfg = cbpi.cache.get("step_types").get(step.type) | type_cfg = cbpi.cache.get("step_types").get(step.type) | ||||
| if type_cfg is None: | 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))) | 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 = type_cfg.get("class")(**cfg) | ||||
| instance.init() | instance.init() | ||||
| # set step instance to ache | # set step instance to ache | ||||
| cbpi.cache["active_step"] = instance | cbpi.cache["active_step"] = instance | ||||
| @@ -185,15 +195,16 @@ def init_after_startup(): | |||||
| if type_cfg is None: | if type_cfg is None: | ||||
| # step type not found. cant restart step | # step type not found. cant restart step | ||||
| return | return | ||||
| cfg = step.stepstate.copy() | 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 = type_cfg.get("class")(**cfg) | ||||
| instance.init() | instance.init() | ||||
| cbpi.cache["active_step"] = instance | cbpi.cache["active_step"] = instance | ||||
| @cbpi.initalizer(order=2000) | @cbpi.initalizer(order=2000) | ||||
| def init(cbpi): | def init(cbpi): | ||||
| print "INITIALIZE STEPS MODULE" | |||||
| StepView.register(cbpi.app, route_base='/api/step') | StepView.register(cbpi.app, route_base='/api/step') | ||||
| def get_all(): | def get_all(): | ||||
| @@ -205,7 +216,7 @@ def init(cbpi): | |||||
| cbpi.add_cache_callback("steps", get_all) | cbpi.add_cache_callback("steps", get_all) | ||||
| @cbpi.backgroundtask(key="step_task", interval=0.1) | @cbpi.backgroundtask(key="step_task", interval=0.1) | ||||
| def execute_step(): | |||||
| def execute_step(api): | |||||
| ''' | ''' | ||||
| Background job which executes the step | Background job which executes the step | ||||
| :return: | :return: | ||||
| @@ -214,9 +225,7 @@ def execute_step(): | |||||
| step = cbpi.cache.get("active_step") | step = cbpi.cache.get("active_step") | ||||
| if step is not None: | if step is not None: | ||||
| step.execute() | step.execute() | ||||
| if step.is_dirty(): | if step.is_dirty(): | ||||
| state = {} | state = {} | ||||
| for field in step.managed_fields: | for field in step.managed_fields: | ||||
| state[field] = step.__getattribute__(field) | state[field] = step.__getattribute__(field) | ||||
| @@ -1,4 +1,5 @@ | |||||
| from flask import json | |||||
| import yaml | |||||
| from flask import json, url_for, Response | |||||
| from flask_classy import FlaskView, route | from flask_classy import FlaskView, route | ||||
| from git import Repo, Git | from git import Repo, Git | ||||
| @@ -101,7 +102,44 @@ class SystemView(FlaskView): | |||||
| def dump(self): | def dump(self): | ||||
| return json.dumps(cbpi.cache) | 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() | @cbpi.initalizer() | ||||
| def init(cbpi): | def init(cbpi): | ||||
| print "INITIALIZE SYSTEM MODULE" | |||||
| SystemView.api = cbpi | |||||
| SystemView.register(cbpi.app, route_base='/api/system') | SystemView.register(cbpi.app, route_base='/api/system') | ||||
| @@ -5,11 +5,12 @@ react = Blueprint('react', __name__, template_folder='templates', static_folder= | |||||
| @cbpi.initalizer(order=10) | @cbpi.initalizer(order=10) | ||||
| def init(cbpi): | def init(cbpi): | ||||
| print "INITIALIZE UI" | |||||
| cbpi.app.register_blueprint(react, url_prefix='/ui') | cbpi.app.register_blueprint(react, url_prefix='/ui') | ||||
| @react.route('/') | |||||
| @react.route('/', methods=["GET"]) | |||||
| def index(): | def index(): | ||||
| return react.send_static_file("index.html") | return react.send_static_file("index.html") | ||||
| @@ -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; | |||||
| } | |||||
| @@ -0,0 +1,5 @@ | |||||
| body { | |||||
| margin: 0; | |||||
| padding: 0; | |||||
| font-family: sans-serif; | |||||
| } | |||||
| @@ -13,6 +13,8 @@ | |||||
| <style> | <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 { | .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; | position: relative; | ||||
| min-height: 1px; | min-height: 1px; | ||||
| @@ -50,11 +52,15 @@ | |||||
| /* Einschränken des sichtbaren Ausschnitts */ | |||||
| </style> | </style> | ||||
| <title>CraftBeerPi 3.0</title> | <title>CraftBeerPi 3.0</title> | ||||
| </head> | </head> | ||||
| <body> | <body> | ||||
| <div id="root"></div> | |||||
| <div id="root" ></div> | |||||
| <script src="static/bundle.js" type="text/javascript"></script> | <script src="static/bundle.js" type="text/javascript"></script> | ||||
| <!-- | <!-- | ||||
| This HTML file is a template. | This HTML file is a template. | ||||
| @@ -11,5 +11,4 @@ requests==2.11.0 | |||||
| Werkzeug==0.11.10 | Werkzeug==0.11.10 | ||||
| httplib2==0.9.2 | httplib2==0.9.2 | ||||
| flask-classy==0.6.10 | flask-classy==0.6.10 | ||||
| GitPython==2.1.3 | |||||
| GitPython==2.1.3 | |||||
| @@ -0,0 +1,18 @@ | |||||
| CREATE TABLE IF NOT EXISTS schema_info | |||||
| ( | |||||
| id INTEGER PRIMARY KEY NOT NULL, | |||||
| version INTEGER, | |||||
| filename VARCHAR(80), | |||||
| creation_data DEFAULT CURRENT_TIMESTAMP | |||||
| ); | |||||
| INSERT OR IGNORE INTO config VALUES ('step_mashin', 'MashStep', 'step', 'Default MashIn Step type for import', NULL ); | |||||
| INSERT OR IGNORE INTO config VALUES ('step_mash', 'MashStep', 'step', 'Default Mash Step type for import', NULL); | |||||
| INSERT OR IGNORE INTO config VALUES ('step_boil', 'BoilStep', 'step', 'Default Boil Step type for import', NULL); | |||||
| INSERT OR IGNORE INTO config VALUES ('step_chil', 'ChilStep', 'step', 'Default Chil Step type for import', NULL); | |||||
| INSERT OR IGNORE INTO config VALUES ('step_mash_kettle', 1, 'kettle', 'Default Mash Tun', NULL); | |||||
| INSERT OR IGNORE INTO config VALUES ('step_boil_kettle', 1, 'kettle', 'Default Boil Tun ', NULL); | |||||
| @@ -0,0 +1 @@ | |||||
| UPDATE config set `options` = '[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27]' WHERE name ='buzzer'; | |||||