| @@ -8,3 +8,7 @@ craftbeerpi.db | |||
| modules/plugins/* | |||
| !modules/plugins/__init__.py | |||
| *.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> | |||
| ## 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. | |||
| @@ -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 | |||
| ); | |||
| ); | |||
| @@ -0,0 +1 @@ | |||
| 3.0.2 | |||
| @@ -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("##########################################") | |||
| @@ -17,10 +17,10 @@ class ActorView(BaseView): | |||
| obj.state = 0 | |||
| obj.power = 100 | |||
| def post_post_callback(self, m): | |||
| def _post_post_callback(self, m): | |||
| self.api.init_actor(m.id) | |||
| def post_put_callback(self, m): | |||
| def _post_put_callback(self, m): | |||
| self.api.init_actor(m.id) | |||
| @@ -60,10 +60,15 @@ class ActorView(BaseView): | |||
| t = self.api.socketio.start_background_task(target=self.toggleTimeJob, id=id, t=t) | |||
| return ('', 204) | |||
| @route('<int:id>/action/<method>', methods=["POST"]) | |||
| def action(self, id, method): | |||
| cbpi.cache.get("actors").get(id).instance.__getattribute__(method)() | |||
| return ('', 204) | |||
| @cbpi.initalizer(order=1000) | |||
| def init(cbpi): | |||
| print "INITIALIZE ACTOR MODULE" | |||
| cbpi.app.logger.info("INITIALIZE ACTOR MODULE") | |||
| ActorView.register(cbpi.app, route_base='/api/actor') | |||
| ActorView.init_cache() | |||
| cbpi.init_actors() | |||
| @@ -166,7 +166,9 @@ def update_addon(name): | |||
| def loadCorePlugins(): | |||
| for filename in os.listdir("./modules/base_plugins"): | |||
| if filename.endswith(".py") or filename.endswith(".pyc"): | |||
| if os.path.isdir("./modules/base_plugins/"+filename) is False: | |||
| continue | |||
| try: | |||
| modules[filename] = import_module("modules.base_plugins.%s" % (filename)) | |||
| @@ -178,7 +180,7 @@ def loadCorePlugins(): | |||
| def loadPlugins(): | |||
| for filename in os.listdir("./modules/plugins"): | |||
| if filename.endswith(".py") or filename.endswith(".pyc"): | |||
| if os.path.isdir("./modules/plugins/" + filename) is False: | |||
| continue | |||
| try: | |||
| modules[filename] = import_module("modules.plugins.%s" % (filename)) | |||
| @@ -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') | |||
| @@ -15,7 +15,9 @@ from modules.core.db import DBModel | |||
| app = Flask(__name__) | |||
| logging.basicConfig(filename='./logs/app.log',level=logging.INFO) | |||
| FORMAT = '%(asctime)-15s - %(levelname)s - %(message)s' | |||
| logging.basicConfig(filename='./logs/app.log',level=logging.INFO, format=FORMAT) | |||
| app.config['SECRET_KEY'] = 'craftbeerpi' | |||
| app.config['UPLOAD_FOLDER'] = './upload' | |||
| @@ -45,7 +47,7 @@ class ComplexEncoder(json.JSONEncoder): | |||
| return None | |||
| app.json_encoder = ComplexEncoder | |||
| socketio = SocketIO(app, json=json) | |||
| socketio = SocketIO(app, json=json, logging=False) | |||
| cbpi = CraftBeerPi(app, socketio) | |||
| app.logger.info("##########################################") | |||
| @@ -7,15 +7,16 @@ from modules.core.step import StepBase | |||
| from modules import cbpi | |||
| @cbpi.step | |||
| class MashStep(StepBase): | |||
| ''' | |||
| Just put the decorator @cbpi.step on top of a method | |||
| ''' | |||
| # Properties | |||
| temp = Property.Number("Temperature", configurable=True) | |||
| kettle = StepProperty.Kettle("Kettle") | |||
| timer = Property.Number("Timer in Minutes", configurable=True) | |||
| temp = Property.Number("Temperature", configurable=True, description="Target Temperature of Mash Step") | |||
| kettle = StepProperty.Kettle("Kettle", description="Kettle in which the mashing takes place") | |||
| timer = Property.Number("Timer in Minutes", configurable=True, description="Timer is started when the target temperature is reached") | |||
| def init(self): | |||
| ''' | |||
| @@ -49,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() | |||
| @@ -10,21 +10,38 @@ from modules.core.props import Property | |||
| @cbpi.sensor | |||
| class DummyTempSensor(SensorActive): | |||
| temp = Property.Number("Temperature", configurable=True, default_value=5) | |||
| temp = Property.Number("Temperature", configurable=True, default_value=5, description="Dummy Temperature as decimal value") | |||
| @cbpi.action("My Custom Action") | |||
| def my_action(self): | |||
| print "HELLO WORLD" | |||
| pass | |||
| def get_unit(self): | |||
| ''' | |||
| :return: Unit of the sensor as string. Should not be longer than 3 characters | |||
| ''' | |||
| return "°C" if self.get_config_parameter("unit", "C") == "C" else "°F" | |||
| def stop(self): | |||
| pass | |||
| SensorActive.stop(self) | |||
| def execute(self): | |||
| while self.is_running(): | |||
| ''' | |||
| Active sensor has to handle his own loop | |||
| :return: | |||
| ''' | |||
| while self.is_running() is True: | |||
| self.data_received(self.temp) | |||
| socketio.sleep(5) | |||
| self.sleep(5) | |||
| @classmethod | |||
| def init_global(cls): | |||
| ''' | |||
| Called one at the startup for all sensors | |||
| :return: | |||
| ''' | |||
| @@ -6,10 +6,10 @@ from modules.core.props import Property | |||
| @cbpi.fermentation_controller | |||
| class Hysteresis(FermenterController): | |||
| heater_offset_min = Property.Number("Heater Offset min", True, 0) | |||
| heater_offset_max = Property.Number("Heater Offset max", True, 0) | |||
| cooler_offset_min = Property.Number("Cooler Offset min", True, 0) | |||
| cooler_offset_max = Property.Number("Cooler Offset max", True, 0) | |||
| heater_offset_min = Property.Number("Heater Offset ON", True, 0, description="Offset as decimal number when the heater is switched on. Should be greater then 'Heater Offset OFF'. For example a value of 2 switches on the heater if the current temperature is 2 degrees below the target temperature") | |||
| heater_offset_max = Property.Number("Heater Offset OFF", True, 0, description="Offset as decimal number when the heater is switched off. Should be smaller then 'Heater Offset ON'. For example a value of 1 switches off the heater if the current temperature is 1 degree below the target temperature") | |||
| cooler_offset_min = Property.Number("Cooler Offset ON", True, 0, description="Offset as decimal number when the cooler is switched on. Should be greater then 'Cooler Offset OFF'. For example a value of 2 switches on the cooler if the current temperature is 2 degrees above the target temperature") | |||
| cooler_offset_max = Property.Number("Cooler Offset OFF", True, 0, description="Offset as decimal number when the cooler is switched off. Should be less then 'Cooler Offset ON'. For example a value of 1 switches off the cooler if the current temperature is 1 degree above the target temperature") | |||
| def stop(self): | |||
| super(FermenterController, self).stop() | |||
| @@ -23,16 +23,16 @@ class Hysteresis(FermenterController): | |||
| target_temp = self.get_target_temp() | |||
| temp = self.get_temp() | |||
| if temp + int(self.heater_offset_min) < target_temp: | |||
| if temp + float(self.heater_offset_min) <= target_temp: | |||
| self.heater_on(100) | |||
| if temp + int(self.heater_offset_max) > target_temp: | |||
| if temp + float(self.heater_offset_max) >= target_temp: | |||
| self.heater_off() | |||
| if temp > target_temp + int(self.cooler_offset_min): | |||
| if temp >= target_temp + float(self.cooler_offset_min): | |||
| self.cooler_on(100) | |||
| if temp < target_temp + int(self.cooler_offset_max): | |||
| if temp <= target_temp + float(self.cooler_offset_max): | |||
| self.cooler_off() | |||
| self.sleep(1) | |||
| self.sleep(1) | |||
| @@ -18,7 +18,7 @@ except Exception as e: | |||
| @cbpi.actor | |||
| class GPIOSimple(ActorBase): | |||
| gpio = Property.Select("GPIO", options=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27]) | |||
| gpio = Property.Select("GPIO", options=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27], description="GPIO to which the actor is connected") | |||
| def init(self): | |||
| GPIO.setup(int(self.gpio), GPIO.OUT) | |||
| @@ -35,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" | |||
| @@ -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() | |||
| @@ -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') | |||
| @@ -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() | |||
| @@ -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() | |||
| @@ -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) | |||
| @@ -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) | |||
| @@ -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) | |||
| @@ -42,13 +42,15 @@ class DBModel(object): | |||
| def get_all(cls): | |||
| cur = get_db().cursor() | |||
| if cls.__order_by__ is not None: | |||
| cur.execute("SELECT * FROM %s ORDER BY '%s'" % (cls.__table_name__,cls.__order_by__)) | |||
| cur.execute("SELECT * FROM %s ORDER BY %s.'%s'" % (cls.__table_name__,cls.__table_name__,cls.__order_by__)) | |||
| else: | |||
| cur.execute("SELECT * FROM %s" % cls.__table_name__) | |||
| if cls.__as_array__ is True: | |||
| result = [] | |||
| for r in cur.fetchall(): | |||
| result.append( cls(r)) | |||
| else: | |||
| result = {} | |||
| @@ -104,7 +106,7 @@ class DBModel(object): | |||
| else: | |||
| data = data + (kwargs.get(f),) | |||
| print query, data | |||
| cur.execute(query, data) | |||
| get_db().commit() | |||
| i = cur.lastrowid | |||
| @@ -0,0 +1,47 @@ | |||
| import sqlite3 | |||
| import os | |||
| from modules import cbpi | |||
| from db import get_db | |||
| def execute_file(curernt_version, data): | |||
| if curernt_version >= data["version"]: | |||
| cbpi.app.logger.info("SKIP DB FILE: %s" % data["file"]) | |||
| return | |||
| try: | |||
| with sqlite3.connect("craftbeerpi.db") as conn: | |||
| with open('./update/%s' % data["file"], 'r') as f: | |||
| d = f.read() | |||
| sqlCommands = d.split(";") | |||
| cur = conn.cursor() | |||
| for s in sqlCommands: | |||
| cur.execute(s) | |||
| cur.execute("INSERT INTO schema_info (version,filename) values (?,?)", (data["version"], data["file"])) | |||
| conn.commit() | |||
| except sqlite3.OperationalError as err: | |||
| print "EXCEPT" | |||
| print err | |||
| @cbpi.initalizer(order=-9999) | |||
| def init(app=None): | |||
| with cbpi.app.app_context(): | |||
| conn = get_db() | |||
| cur = conn.cursor() | |||
| current_version = None | |||
| try: | |||
| cur.execute("SELECT max(version) as m FROM schema_info") | |||
| m = cur.fetchone() | |||
| current_version = m["m"] | |||
| except: | |||
| pass | |||
| result = [] | |||
| for filename in os.listdir("./update"): | |||
| if filename.endswith(".sql"): | |||
| d = {"version": int(filename[:filename.index('_')]), "file": filename} | |||
| result.append(d) | |||
| execute_file(current_version, d) | |||
| @@ -5,16 +5,19 @@ class Base(object): | |||
| @classmethod | |||
| def init_global(cls): | |||
| print "GLOBAL ACTOR INIT" | |||
| pass | |||
| def get_config_parameter(self, key, default_value): | |||
| return self.api.get_config_parameter(key, default_value) | |||
| def sleep(self, seconds): | |||
| self.api.socketio.sleep(seconds) | |||
| def init(self): | |||
| print "INIT BASE" | |||
| pass | |||
| def stop(self): | |||
| print "STOP HARDWARE" | |||
| pass | |||
| def update(self, **kwds): | |||
| pass | |||
| @@ -67,15 +70,16 @@ class SensorActive(SensorBase): | |||
| __running = False | |||
| def is_running(self): | |||
| return self.__running | |||
| def init(self): | |||
| self.__running = True | |||
| print "INIT ACTIVE SENSOR" | |||
| def stop(self): | |||
| self.__running = False | |||
| def execute(self): | |||
| pass | |||
| @@ -95,10 +99,10 @@ class ActorBase(Base): | |||
| return 1 | |||
| def set_power(self, power): | |||
| print "SET POWER TO %s" % power | |||
| pass | |||
| def on(self, power=0): | |||
| print "ON" | |||
| pass | |||
| def off(self): | |||
| print "OFF" | |||
| pass | |||
| @@ -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 | |||
| @@ -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 "-------------" | |||
| @@ -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() | |||
| @@ -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 | |||
| @@ -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') | |||
| @@ -32,10 +32,9 @@ def messageEvent(message, **kwargs): | |||
| :param kwargs: other parameter | |||
| :return: None | |||
| """ | |||
| msg = {"id": len(cbpi.cache["messages"]), "type": "info", "message": message, "read": False} | |||
| cbpi.cache["messages"].append(msg) | |||
| cbpi.emit('MESSAGE', msg,) | |||
| if message["timeout"] is None: | |||
| cbpi.cache["messages"].append(message) | |||
| cbpi.emit("NOTIFY", message) | |||
| @cbpi.initalizer(order=2) | |||
| def init(cbpi): | |||
| @@ -44,9 +43,8 @@ def init(cbpi): | |||
| :param app: the flask app | |||
| :return: None | |||
| """ | |||
| print "INITIALIZE MESSAGE MODULE" | |||
| msg = {"id": len(cbpi.cache["messages"]), "type": "info", "headline": "Support CraftBeerPi with your donation", "message": "You will find the PayPay Donation button in the system menu" , "read": False} | |||
| cbpi.cache["messages"].append(msg) | |||
| if cbpi.get_config_parameter("donation_notification", "YES") == "YES": | |||
| msg = {"id": len(cbpi.cache["messages"]), "type": "info", "headline": "Support CraftBeerPi with your donation", "message": "You will find the PayPay Donation button in the system menu" , "read": False} | |||
| cbpi.cache["messages"].append(msg) | |||
| NotificationView.register(cbpi.app, route_base='/api/notification') | |||
| @@ -0,0 +1,3 @@ | |||
| import beerxml | |||
| import kbh | |||
| import restapi | |||
| @@ -0,0 +1,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 | |||
| cache_key = "sensors" | |||
| @route('<int:id>/action/<method>', methods=["POST"]) | |||
| def action(self, id, method): | |||
| def post_post_callback(self, m): | |||
| cbpi.cache.get("sensors").get(id).instance.__getattribute__(method)() | |||
| return ('', 204) | |||
| def _post_post_callback(self, m): | |||
| cbpi.init_sensor(m.id) | |||
| def post_put_callback(self, m): | |||
| def _post_put_callback(self, m): | |||
| cbpi.stop_sensor(m.id) | |||
| cbpi.init_sensor(m.id) | |||
| def pre_delete_callback(self, m): | |||
| def _pre_delete_callback(self, m): | |||
| cbpi.stop_sensor(m.id) | |||
| @cbpi.initalizer(order=1000) | |||
| def init(cbpi): | |||
| print "INITIALIZE SENSOR MODULE" | |||
| SensorView.register(cbpi.app, route_base='/api/sensor') | |||
| SensorView.init_cache() | |||
| cbpi.init_sensors() | |||
| @cbpi.backgroundtask(key="read_passiv_sensor", interval=5) | |||
| def read_passive_sensor(): | |||
| def read_passive_sensor(api): | |||
| """ | |||
| background process that reads all passive sensors in interval of 1 second | |||
| :return: None | |||
| @@ -16,7 +16,7 @@ def getserial(): | |||
| @cbpi.initalizer(order=9999) | |||
| def sendStats(cbpi): | |||
| print "INITIALIZE STATS" | |||
| try: | |||
| serial = getserial() | |||
| @@ -1,5 +1,5 @@ | |||
| import time | |||
| from flask import json | |||
| from flask import json, request | |||
| from flask_classy import route | |||
| from modules import DBModel, cbpi, get_db | |||
| @@ -21,9 +21,9 @@ class Step(DBModel): | |||
| return r.get("order") | |||
| @classmethod | |||
| def get_by_state(cls, state): | |||
| def get_by_state(cls, state, order=True): | |||
| cur = get_db().cursor() | |||
| cur.execute("SELECT * FROM %s WHERE state = ?" % cls.__table_name__, state) | |||
| cur.execute("SELECT * FROM %s WHERE state = ? ORDER BY %s.'order'" % (cls.__table_name__,cls.__table_name__,), state) | |||
| r = cur.fetchone() | |||
| if r is not None: | |||
| return cls(r) | |||
| @@ -54,13 +54,29 @@ class Step(DBModel): | |||
| cur.execute("UPDATE %s SET stepstate = ? WHERE id =?" % cls.__table_name__, (json.dumps(state),id)) | |||
| get_db().commit() | |||
| @classmethod | |||
| def sort(cls, new_order): | |||
| cur = get_db().cursor() | |||
| for e in new_order: | |||
| cur.execute("UPDATE %s SET '%s' = ? WHERE id = ?" % (cls.__table_name__, "order"), (e[1], e[0])) | |||
| get_db().commit() | |||
| class StepView(BaseView): | |||
| model = Step | |||
| def pre_post_callback(self, data): | |||
| def _pre_post_callback(self, data): | |||
| order = self.model.get_max_order() | |||
| data["order"] = 1 if order is None else order + 1 | |||
| data["state"] = "I" | |||
| @route('/sort', methods=["POST"]) | |||
| def sort_steps(self): | |||
| Step.sort(request.json) | |||
| cbpi.emit("UPDATE_ALL_STEPS", self.model.get_all()) | |||
| return ('', 204) | |||
| @route('/', methods=["DELETE"]) | |||
| def deleteAll(self): | |||
| self.model.delete_all() | |||
| @@ -69,16 +85,12 @@ class StepView(BaseView): | |||
| @route('/action/<method>', methods=["POST"]) | |||
| def action(self, method): | |||
| cbpi.cache["active_step"].__getattribute__(method)() | |||
| return ('', 204) | |||
| @route('/reset', methods=["POST"]) | |||
| def reset(self): | |||
| self.model.reset_all_steps() | |||
| #db.session.commit() | |||
| self.stop_step() | |||
| cbpi.emit("UPDATE_ALL_STEPS", self.model.get_all()) | |||
| return ('', 204) | |||
| @@ -115,7 +127,6 @@ class StepView(BaseView): | |||
| return ('', 204) | |||
| def init_step(self, step): | |||
| cbpi.log_action("Start Step %s" % step.name) | |||
| type_cfg = cbpi.cache.get("step_types").get(step.type) | |||
| if type_cfg is None: | |||
| @@ -128,7 +139,6 @@ class StepView(BaseView): | |||
| cfg.update(dict(name=step.name, api=cbpi, id=step.id, timer_end=None, managed_fields=get_manged_fields_as_array(type_cfg))) | |||
| instance = type_cfg.get("class")(**cfg) | |||
| instance.init() | |||
| # set step instance to ache | |||
| cbpi.cache["active_step"] = instance | |||
| @@ -185,15 +195,16 @@ def init_after_startup(): | |||
| if type_cfg is None: | |||
| # step type not found. cant restart step | |||
| return | |||
| cfg = step.stepstate.copy() | |||
| cfg.update(dict(api=cbpi, id=step.id, timer_end=None, managed_fields=get_manged_fields_as_array(type_cfg))) | |||
| cfg.update(dict(api=cbpi, id=step.id, managed_fields=get_manged_fields_as_array(type_cfg))) | |||
| instance = type_cfg.get("class")(**cfg) | |||
| instance.init() | |||
| cbpi.cache["active_step"] = instance | |||
| @cbpi.initalizer(order=2000) | |||
| def init(cbpi): | |||
| print "INITIALIZE STEPS MODULE" | |||
| StepView.register(cbpi.app, route_base='/api/step') | |||
| def get_all(): | |||
| @@ -205,7 +216,7 @@ def init(cbpi): | |||
| cbpi.add_cache_callback("steps", get_all) | |||
| @cbpi.backgroundtask(key="step_task", interval=0.1) | |||
| def execute_step(): | |||
| def execute_step(api): | |||
| ''' | |||
| Background job which executes the step | |||
| :return: | |||
| @@ -214,9 +225,7 @@ def execute_step(): | |||
| step = cbpi.cache.get("active_step") | |||
| if step is not None: | |||
| step.execute() | |||
| if step.is_dirty(): | |||
| state = {} | |||
| for field in step.managed_fields: | |||
| state[field] = step.__getattribute__(field) | |||
| @@ -1,4 +1,5 @@ | |||
| from flask import json | |||
| import yaml | |||
| from flask import json, url_for, Response | |||
| from flask_classy import FlaskView, route | |||
| from git import Repo, Git | |||
| @@ -101,7 +102,44 @@ class SystemView(FlaskView): | |||
| def dump(self): | |||
| return json.dumps(cbpi.cache) | |||
| @route('/endpoints', methods=['GET']) | |||
| def endpoints(self): | |||
| import urllib | |||
| output = [] | |||
| vf = self.api.app.view_functions | |||
| for f in self.api.app.view_functions: | |||
| print f | |||
| endpoints = {} | |||
| re = { | |||
| "swagger": "2.0", | |||
| "host": "", | |||
| "info": { | |||
| "description":"", | |||
| "version": "", | |||
| "title": "CraftBeerPi" | |||
| }, | |||
| "schemes": ["http"], | |||
| "paths": endpoints} | |||
| for rule in self.api.app.url_map.iter_rules(): | |||
| r = rule | |||
| endpoints[rule.rule] = {} | |||
| if "HEAD" in r.methods: r.methods.remove("HEAD") | |||
| if "OPTIONS" in r.methods: r.methods.remove("OPTIONS") | |||
| for m in rule.methods: | |||
| endpoints[rule.rule][m] = dict(summary="", description="", consumes=["application/json"],produces=["application/json"]) | |||
| with open("config/version.yaml", 'r') as stream: | |||
| y = yaml.load(stream) | |||
| pprint.pprint(y) | |||
| pprint.pprint(re) | |||
| return Response(yaml.dump(re), mimetype='text/yaml') | |||
| @cbpi.initalizer() | |||
| def init(cbpi): | |||
| print "INITIALIZE SYSTEM MODULE" | |||
| SystemView.api = cbpi | |||
| SystemView.register(cbpi.app, route_base='/api/system') | |||
| @@ -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") | |||
| @@ -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> | |||
| .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. | |||
| @@ -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 | |||
| @@ -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'; | |||