- Major API Changes in Core Module - Will be changed further. Still work in progresstags/3.1_alpha
| @@ -1,72 +0,0 @@ | |||
| import json | |||
| import pprint | |||
| import sys, os | |||
| from flask import Flask, render_template, redirect | |||
| from flask_socketio import SocketIO, emit | |||
| import logging | |||
| # Define the WSGI application object | |||
| from app_config import * | |||
| import pprint | |||
| from modules.core.db import get_db | |||
| @app.route('/') | |||
| def index(): | |||
| return redirect('ui') | |||
| # Define the database object which is imported | |||
| # by modules and controllers | |||
| import modules.steps | |||
| import modules.config | |||
| import modules.logs | |||
| import modules.sensors | |||
| import modules.actor | |||
| import modules.notification | |||
| import modules.fermenter | |||
| from modules.addon.endpoints import initPlugins | |||
| import modules.ui | |||
| import modules.system | |||
| import modules.buzzer | |||
| import modules.stats | |||
| import modules.kettle | |||
| import modules.recipe_import | |||
| import modules.core.db_mirgrate | |||
| from app_config import cbpi | |||
| # Build the database: | |||
| # This will create the database file using SQLAlchemy | |||
| pp = pprint.PrettyPrinter(indent=6) | |||
| def init_db(): | |||
| print "INIT DB" | |||
| with app.app_context(): | |||
| db = get_db() | |||
| try: | |||
| with app.open_resource('../config/schema.sql', mode='r') as f: | |||
| db.cursor().executescript(f.read()) | |||
| db.commit() | |||
| except Exception as e: | |||
| pass | |||
| init_db() | |||
| initPlugins() | |||
| cbpi.run_init() | |||
| cbpi.run_background_processes() | |||
| app.logger.info("##########################################") | |||
| app.logger.info("### STARTUP COMPLETE") | |||
| app.logger.info("##########################################") | |||
| @@ -0,0 +1,31 @@ | |||
| import json | |||
| from flask_classy import FlaskView, route | |||
| from modules.core.core import cbpi | |||
| class ActionView(FlaskView): | |||
| @route('/<action>', methods=['POST']) | |||
| def action(self, action): | |||
| """ | |||
| Call global action button | |||
| --- | |||
| tags: | |||
| - action | |||
| responses: | |||
| 200: | |||
| description: action invoked | |||
| """ | |||
| print self.cbpi.cache["actions"] | |||
| self.cbpi.cache["actions"][action]["function"](self.cbpi) | |||
| return ('',204) | |||
| @cbpi.addon.core.initializer() | |||
| def init(cbpi): | |||
| """ | |||
| Initializer for the message module | |||
| :param app: the flask app | |||
| :return: None | |||
| """ | |||
| ActionView.cbpi = cbpi | |||
| ActionView.register(cbpi._app, route_base='/api/action') | |||
| @@ -1,74 +1,183 @@ | |||
| import time | |||
| from flask_classy import route | |||
| from modules import DBModel, cbpi | |||
| from modules.core.baseview import BaseView | |||
| class Actor(DBModel): | |||
| __fields__ = ["name","type", "config", "hide"] | |||
| __table_name__ = "actor" | |||
| __json_fields__ = ["config"] | |||
| class ActorView(BaseView): | |||
| model = Actor | |||
| cache_key = "actors" | |||
| @classmethod | |||
| def post_init_callback(self, obj): | |||
| obj.state = 0 | |||
| obj.power = 100 | |||
| def _post_post_callback(self, m): | |||
| self.api.init_actor(m.id) | |||
| def _post_put_callback(self, m): | |||
| self.api.init_actor(m.id) | |||
| @route("<int:id>/switch/on", methods=["POST"]) | |||
| def on(self, id): | |||
| self.api.switch_actor_on(id) | |||
| return ('', 204) | |||
| @route("<int:id>/switch/off", methods=["POST"]) | |||
| def off(self, id): | |||
| self.api.switch_actor_off(id) | |||
| return ('', 204) | |||
| @route("<int:id>/power/<int:power>", methods=["POST"]) | |||
| def power(self, id, power): | |||
| self.api.actor_power(id, power) | |||
| return ('', 204) | |||
| @route("<int:id>/toggle", methods=["POST"]) | |||
| def toggle(self, id): | |||
| if self.api.cache.get("actors").get(id).state == 0: | |||
| self.on(id) | |||
| else: | |||
| self.off(id) | |||
| return ('', 204) | |||
| def toggleTimeJob(self, id, t): | |||
| self.api.cache.get("actors").get(int(id)).timer = int(time.time()) + int(t) | |||
| self.toggle(int(id)) | |||
| self.api.socketio.sleep(t) | |||
| self.api.cache.get("actors").get(int(id)).timer = None | |||
| self.toggle(int(id)) | |||
| @route("/<id>/toggle/<int:t>", methods=["POST"]) | |||
| def toggleTime(self, id, t): | |||
| 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): | |||
| ActorView.register(cbpi.app, route_base='/api/actor') | |||
| ActorView.init_cache() | |||
| cbpi.init_actors() | |||
| import time | |||
| from flask_classy import route | |||
| from modules.core.db import DBModel | |||
| from modules.core.core import cbpi | |||
| from modules.core.baseview import BaseView | |||
| from modules.database.dbmodel import Actor | |||
| class ActorView(BaseView): | |||
| model = Actor | |||
| cache_key = "actors" | |||
| @classmethod | |||
| def post_init_callback(self, obj): | |||
| obj.state = 0 | |||
| obj.power = 100 | |||
| def _post_post_callback(self, m): | |||
| self.api.actor.init_one(m.id) | |||
| def _post_put_callback(self, m): | |||
| self.api.actor.init_one(m.id) | |||
| @route("<int:id>/switch/on", methods=["POST"]) | |||
| def on(self, id): | |||
| """ | |||
| Switch actor on | |||
| --- | |||
| tags: | |||
| - actor | |||
| parameters: | |||
| - in: path | |||
| name: id | |||
| schema: | |||
| type: integer | |||
| required: true | |||
| description: Numeric ID of the actor | |||
| responses: | |||
| 200: | |||
| description: Actor switched on | |||
| """ | |||
| self.api.actor.on(id) | |||
| return ('', 204) | |||
| @route("<int:id>/switch/off", methods=["POST"]) | |||
| def off(self, id): | |||
| """ | |||
| Switch actor off | |||
| --- | |||
| tags: | |||
| - actor | |||
| parameters: | |||
| - in: path | |||
| name: id | |||
| schema: | |||
| type: integer | |||
| required: true | |||
| description: Numeric ID of the actor | |||
| responses: | |||
| 200: | |||
| description: Actor switched off | |||
| """ | |||
| self.api.actor.off(id) | |||
| return ('', 204) | |||
| @route("<int:id>/power/<int:power>", methods=["POST"]) | |||
| def power(self, id, power): | |||
| """ | |||
| Set Actor Power | |||
| --- | |||
| tags: | |||
| - actor | |||
| parameters: | |||
| - in: path | |||
| name: id | |||
| schema: | |||
| type: integer | |||
| required: true | |||
| description: Numeric ID of the actor | |||
| - in: path | |||
| name: power | |||
| schema: | |||
| type: integer | |||
| required: true | |||
| description: Power value between 0 - 100 | |||
| responses: | |||
| 200: | |||
| description: Actor power set | |||
| """ | |||
| self.api.actor.power(id, power) | |||
| return ('', 204) | |||
| @route("<int:id>/toggle", methods=["POST"]) | |||
| def toggle(self, id): | |||
| """ | |||
| Toggle Actor | |||
| --- | |||
| tags: | |||
| - actor | |||
| parameters: | |||
| - in: path | |||
| name: id | |||
| schema: | |||
| type: integer | |||
| required: true | |||
| description: Numeric ID of the actor | |||
| responses: | |||
| 200: | |||
| description: Actor toggled | |||
| """ | |||
| if self.api.cache.get("actors").get(id).state == 0: | |||
| self.on(id) | |||
| else: | |||
| self.off(id) | |||
| return ('', 204) | |||
| def toggleTimeJob(self, id, t): | |||
| self.api.cache.get("actors").get(int(id)).timer = int(time.time()) + int(t) | |||
| self.toggle(int(id)) | |||
| self.api._socketio.sleep(t) | |||
| self.api.cache.get("actors").get(int(id)).timer = None | |||
| self.toggle(int(id)) | |||
| @route("/<id>/toggle/<int:t>", methods=["POST"]) | |||
| def toggleTime(self, id, t): | |||
| """ | |||
| Toggle Actor for a defined time | |||
| --- | |||
| tags: | |||
| - actor | |||
| parameters: | |||
| - in: path | |||
| name: id | |||
| schema: | |||
| type: integer | |||
| required: true | |||
| description: Numeric ID of the actor | |||
| - in: path | |||
| name: time | |||
| schema: | |||
| type: integer | |||
| required: true | |||
| description: time in seconds | |||
| responses: | |||
| 200: | |||
| description: Actor toggled | |||
| """ | |||
| 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): | |||
| """ | |||
| Actor Action | |||
| --- | |||
| tags: | |||
| - actor | |||
| parameters: | |||
| - in: path | |||
| name: id | |||
| schema: | |||
| type: integer | |||
| required: true | |||
| description: Numeric ID of the actor | |||
| - in: path | |||
| name: method | |||
| schema: | |||
| type: string | |||
| required: true | |||
| description: action method name | |||
| responses: | |||
| 200: | |||
| description: Actor Action called | |||
| """ | |||
| cbpi.cache.get("actors").get(id).instance.__getattribute__(method)() | |||
| return ('', 204) | |||
| @cbpi.addon.core.initializer(order=1000) | |||
| def init(cbpi): | |||
| ActorView.register(cbpi._app, route_base='/api/actor') | |||
| ActorView.init_cache() | |||
| #cbpi.init_actors() | |||
| @@ -1 +0,0 @@ | |||
| import endpoints | |||
| @@ -1,199 +0,0 @@ | |||
| import json | |||
| import sys | |||
| from flask import Blueprint, request, send_from_directory | |||
| from importlib import import_module | |||
| from modules import socketio, cbpi | |||
| from git import Repo | |||
| import os | |||
| import requests | |||
| import yaml | |||
| import shutil | |||
| blueprint = Blueprint('addon', __name__) | |||
| modules = {} | |||
| def merge(source, destination): | |||
| """ | |||
| Helper method to merge two dicts | |||
| :param source: | |||
| :param destination: | |||
| :return: | |||
| """ | |||
| for key, value in source.items(): | |||
| if isinstance(value, dict): | |||
| # get node or create one | |||
| node = destination.setdefault(key, {}) | |||
| merge(value, node) | |||
| else: | |||
| destination[key] = value | |||
| return destination | |||
| @blueprint.route('/', methods=['GET']) | |||
| def getPlugins(): | |||
| """ | |||
| Endpoint for all plugins | |||
| :return: | |||
| """ | |||
| result = [] | |||
| for filename in os.listdir("./modules/plugins"): | |||
| if filename.endswith(".DS_Store") or filename.endswith(".py") or filename.endswith(".pyc"): | |||
| continue | |||
| result.append(filename) | |||
| return json.dumps(result) | |||
| @blueprint.route('/<name>', methods=['GET']) | |||
| def getFile(name): | |||
| """ | |||
| Returns plugin code | |||
| :param name: plugin name | |||
| :return: the plugin code from __init__.py | |||
| """ | |||
| return send_from_directory('./plugins/'+name, "__init__.py") | |||
| @blueprint.route('/<name>', methods=['PUT']) | |||
| def createPlugin(name): | |||
| """ | |||
| Create a new plugin file | |||
| :param name: the plugin name | |||
| :return: empty http response 204 | |||
| """ | |||
| if not os.path.exists("./modules/plugins/"+name): | |||
| os.makedirs("./modules/plugins/"+name) | |||
| with open("./modules/plugins/" + name + "/__init__.py", "wb") as fo: | |||
| fo.write("") | |||
| cbpi.emit_message("PLUGIN %s CREATED" % (name)) | |||
| return ('', 204) | |||
| else: | |||
| cbpi.emit_message("Failed to create plugin %s. Name arlready in use" % (name)) | |||
| return ('', 500) | |||
| @blueprint.route('/<name>', methods=['POST']) | |||
| def saveFile(name): | |||
| """ | |||
| save plugin code. code is provides via http body | |||
| :param name: the plugin name | |||
| :return: empty http reponse | |||
| """ | |||
| with open("./modules/plugins/"+name+"/__init__.py", "wb") as fo: | |||
| fo.write(request.get_data()) | |||
| cbpi.emit_message("PLUGIN %s SAVED" % (name)) | |||
| return ('', 204) | |||
| @blueprint.route('/<name>', methods=['DELETE']) | |||
| def deletePlugin(name): | |||
| """ | |||
| Delete plugin | |||
| :param name: plugin name | |||
| :return: HTTP 204 if ok - HTTP 500 if plugin not exists | |||
| """ | |||
| if os.path.isdir("./modules/plugins/"+name) is False: | |||
| return ('Dir Not found', 500) | |||
| shutil.rmtree("./modules/plugins/"+name) | |||
| cbpi.notify("Plugin deleted", "Plugin %s deleted successfully" % name) | |||
| return ('', 204) | |||
| @blueprint.route('/<name>/reload/', methods=['POST']) | |||
| def reload(name): | |||
| """ | |||
| hot reload plugnin | |||
| :param name: | |||
| :return: | |||
| """ | |||
| try: | |||
| if name in cache["modules"]: | |||
| reload(cache["modules"][name]) | |||
| cbpi.emit_message("REALOD OF PLUGIN %s SUCCESSFUL" % (name)) | |||
| return ('', 204) | |||
| else: | |||
| cache["modules"][name] = import_module("modules.plugins.%s" % (name)) | |||
| return ('', 204) | |||
| except Exception as e: | |||
| cbpi.emit_message("REALOD OF PLUGIN %s FAILED" % (name)) | |||
| return json.dumps(e.message) | |||
| @blueprint.route('/list', methods=['GET']) | |||
| def plugins(): | |||
| """ | |||
| Read the central plugin yaml to get a list of all official plugins | |||
| :return: | |||
| """ | |||
| response = requests.get("https://raw.githubusercontent.com/Manuel83/craftbeerpi-plugins/master/plugins.yaml") | |||
| cbpi.cache["plugins"] = merge(yaml.load(response.text), cbpi.cache["plugins"]) | |||
| for key, value in cbpi.cache["plugins"].iteritems(): | |||
| value["installed"] = os.path.isdir("./modules/plugins/%s/" % (key)) | |||
| return json.dumps(cbpi.cache["plugins"]) | |||
| @blueprint.route('/<name>/download', methods=['POST']) | |||
| def download_addon(name): | |||
| plugin = cbpi.cache["plugins"].get(name) | |||
| plugin["loading"] = True | |||
| if plugin is None: | |||
| return ('', 404) | |||
| try: | |||
| Repo.clone_from(plugin.get("repo_url"), "./modules/plugins/%s/" % (name)) | |||
| cbpi.notify("Download successful", "Plugin %s downloaded successfully" % name) | |||
| finally: | |||
| plugin["loading"] = False | |||
| return ('', 204) | |||
| @blueprint.route('/<name>/update', methods=['POST']) | |||
| def update_addon(name): | |||
| repo = Repo("./modules/plugins/%s/" % (name)) | |||
| o = repo.remotes.origin | |||
| info = o.pull() | |||
| cbpi.notify("Plugin Updated", "Plugin %s updated successfully. Please restart the system" % name) | |||
| return ('', 204) | |||
| def loadCorePlugins(): | |||
| for filename in os.listdir("./modules/base_plugins"): | |||
| if os.path.isdir("./modules/base_plugins/"+filename) is False: | |||
| continue | |||
| try: | |||
| modules[filename] = import_module("modules.base_plugins.%s" % (filename)) | |||
| except Exception as e: | |||
| cbpi.notify("Failed to load plugin %s " % filename, str(e), type="danger", timeout=None) | |||
| cbpi.app.logger.error(e) | |||
| def loadPlugins(): | |||
| for filename in os.listdir("./modules/plugins"): | |||
| if os.path.isdir("./modules/plugins/" + filename) is False: | |||
| continue | |||
| try: | |||
| modules[filename] = import_module("modules.plugins.%s" % (filename)) | |||
| except Exception as e: | |||
| cbpi.notify("Failed to load plugin %s " % filename, str(e), type="danger", timeout=None) | |||
| cbpi.app.logger.error(e) | |||
| #@cbpi.initalizer(order=1) | |||
| def initPlugins(): | |||
| loadCorePlugins() | |||
| loadPlugins() | |||
| @cbpi.initalizer(order=2) | |||
| def init(cbpi): | |||
| cbpi.app.register_blueprint(blueprint, url_prefix='/api/editor') | |||
| @@ -1,55 +0,0 @@ | |||
| import json | |||
| import sys, os | |||
| from flask import Flask, render_template, redirect, json, g | |||
| from flask_socketio import SocketIO, emit | |||
| import logging | |||
| from modules.core.core import CraftBeerPi, ActorBase, SensorBase | |||
| from modules.core.db import DBModel | |||
| app = Flask(__name__) | |||
| 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' | |||
| @app.teardown_appcontext | |||
| def close_connection(exception): | |||
| db = getattr(g, '_database', None) | |||
| if db is not None: | |||
| db.close() | |||
| class ComplexEncoder(json.JSONEncoder): | |||
| def default(self, obj): | |||
| try: | |||
| if isinstance(obj, DBModel): | |||
| return obj.__dict__ | |||
| elif isinstance(obj, ActorBase): | |||
| return obj.state() | |||
| elif isinstance(obj, SensorBase): | |||
| return obj.get_value() | |||
| elif hasattr(obj, "callback"): | |||
| return obj() | |||
| else: | |||
| return None | |||
| except TypeError as e: | |||
| pass | |||
| return None | |||
| app.json_encoder = ComplexEncoder | |||
| socketio = SocketIO(app, json=json, logging=False) | |||
| cbpi = CraftBeerPi(app, socketio) | |||
| app.logger.info("##########################################") | |||
| app.logger.info("### NEW STARTUP Version 3.0") | |||
| app.logger.info("##########################################") | |||
| @@ -0,0 +1,51 @@ | |||
| from modules.core.baseapi import Buzzer | |||
| from modules.core.basetypes import Actor, KettleController, FermenterController | |||
| from modules.core.core import cbpi | |||
| @cbpi.addon.actor.type("Dummy Actor") | |||
| class Dummy(Actor): | |||
| @cbpi.addon.actor.action("WOHOO") | |||
| def myaction(self): | |||
| print "HALLO!!!" | |||
| 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" | |||
| @cbpi.addon.kettle.controller() | |||
| class MyController(KettleController): | |||
| def run(self): | |||
| while self.is_running(): | |||
| print "HALLO" | |||
| self.sleep(1) | |||
| @cbpi.addon.fermenter.controller() | |||
| class MyController2(FermenterController): | |||
| def run(self): | |||
| while self.is_running(): | |||
| print "HALLO" | |||
| self.sleep(1) | |||
| @cbpi.addon.core.initializer(order=200) | |||
| def init(cbpi): | |||
| class MyBuzzer(Buzzer): | |||
| def beep(self): | |||
| print "BEEEEEEP" | |||
| cbpi.buzzer = MyBuzzer() | |||
| @@ -1,223 +0,0 @@ | |||
| # -*- coding: utf-8 -*- | |||
| import time | |||
| from modules.core.props import Property, StepProperty | |||
| 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, 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): | |||
| ''' | |||
| 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 execute(self): | |||
| ''' | |||
| This method is execute in an interval | |||
| :return: | |||
| ''' | |||
| # Check if Target Temp is reached | |||
| if self.get_kettle_temp(self.kettle) >= float(self.temp): | |||
| # Check if Timer is Running | |||
| if self.is_timer_finished() is None: | |||
| self.start_timer(int(self.timer) * 60) | |||
| # Check if timer finished and go to next step | |||
| if self.is_timer_finished() == True: | |||
| self.notify("Mash Step Completed!", "Starting the next step", timeout=None) | |||
| self.next() | |||
| @cbpi.step | |||
| class MashInStep(StepBase): | |||
| ''' | |||
| Just put the decorator @cbpi.step on top of a method | |||
| ''' | |||
| # Properties | |||
| temp = Property.Number("Temperature", configurable=True, description="Target Temperature of Mash Step") | |||
| kettle = 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 | |||
| :return: | |||
| ''' | |||
| # set target tep | |||
| self.s = False | |||
| self.set_target_temp(self.temp, self.kettle) | |||
| def execute(self): | |||
| ''' | |||
| This method is execute in an interval | |||
| :return: | |||
| ''' | |||
| # Check if Target Temp is reached | |||
| if self.get_kettle_temp(self.kettle) >= float(self.temp) and self.s is False: | |||
| self.s = True | |||
| self.notify("Step Temp Reached!", "Please press the next button to continue", timeout=None) | |||
| @cbpi.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) >= float(self.temp): | |||
| # Check if Timer is Running | |||
| if self.is_timer_finished() is None: | |||
| self.start_timer(int(self.timer) * 60) | |||
| else: | |||
| self.check_hop_timer(1, self.hop_1) | |||
| self.check_hop_timer(2, self.hop_2) | |||
| self.check_hop_timer(3, self.hop_3) | |||
| # Check if timer finished and go to next step | |||
| if self.is_timer_finished() == True: | |||
| self.notify("Boil Step Completed!", "Starting the next step", timeout=None) | |||
| self.next() | |||
| @@ -1,53 +0,0 @@ | |||
| # -*- coding: utf-8 -*- | |||
| import subprocess | |||
| import time | |||
| from modules import cbpi, socketio | |||
| from modules.core.hardware import SensorActive | |||
| from modules import cbpi | |||
| from modules.core.props import Property | |||
| @cbpi.sensor | |||
| class DummyTempSensor(SensorActive): | |||
| 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): | |||
| SensorActive.stop(self) | |||
| def execute(self): | |||
| ''' | |||
| Active sensor has to handle his own loop | |||
| :return: | |||
| ''' | |||
| while self.is_running() is True: | |||
| self.data_received(self.temp) | |||
| self.sleep(5) | |||
| @classmethod | |||
| def init_global(cls): | |||
| ''' | |||
| Called one at the startup for all sensors | |||
| :return: | |||
| ''' | |||
| @@ -1,38 +0,0 @@ | |||
| from modules import cbpi | |||
| from modules.core.controller import KettleController, FermenterController | |||
| from modules.core.props import Property | |||
| @cbpi.fermentation_controller | |||
| class Hysteresis(FermenterController): | |||
| 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() | |||
| self.heater_off() | |||
| self.cooler_off() | |||
| def run(self): | |||
| while self.is_running(): | |||
| target_temp = self.get_target_temp() | |||
| temp = self.get_temp() | |||
| if temp + float(self.heater_offset_min) <= target_temp: | |||
| self.heater_on(100) | |||
| if temp + float(self.heater_offset_max) >= target_temp: | |||
| self.heater_off() | |||
| if temp >= target_temp + float(self.cooler_offset_min): | |||
| self.cooler_on(100) | |||
| if temp <= target_temp + float(self.cooler_offset_max): | |||
| self.cooler_off() | |||
| self.sleep(1) | |||
| @@ -1,107 +0,0 @@ | |||
| # -*- coding: utf-8 -*- | |||
| import time | |||
| from modules import cbpi | |||
| from modules.core.hardware import ActorBase, SensorPassive, SensorActive | |||
| from modules.core.props import Property | |||
| try: | |||
| import RPi.GPIO as GPIO | |||
| GPIO.setmode(GPIO.BCM) | |||
| except Exception as e: | |||
| print e | |||
| pass | |||
| @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], description="GPIO to which the actor is connected") | |||
| def init(self): | |||
| GPIO.setup(int(self.gpio), GPIO.OUT) | |||
| GPIO.output(int(self.gpio), 0) | |||
| def on(self, power=0): | |||
| print "GPIO ON %s" % str(self.gpio) | |||
| GPIO.output(int(self.gpio), 1) | |||
| def off(self): | |||
| print "GPIO OFF" | |||
| GPIO.output(int(self.gpio), 0) | |||
| @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], description="GPIO to which the actor is connected") | |||
| duty_cylce = Property.Number("Duty Cycle", configurable=True) | |||
| p = None | |||
| power = 100 | |||
| def init(self): | |||
| GPIO.setup(int(self.gpio), GPIO.OUT) | |||
| GPIO.output(int(self.gpio), 0) | |||
| def on(self, power=None): | |||
| if power is not None: | |||
| self.power = int(power) | |||
| if self.duty_cylce is None: | |||
| duty_cylce = 50 | |||
| self.p = GPIO.PWM(int(self.gpio), int(self.duty_cylce)) | |||
| 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) | |||
| def off(self): | |||
| print "GPIO OFF" | |||
| self.p.stop() | |||
| @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], description="GPIO to which the actor is connected") | |||
| def init(self): | |||
| GPIO.setup(int(self.gpio), GPIO.OUT) | |||
| GPIO.output(int(self.gpio), 1) | |||
| def on(self, power=0): | |||
| GPIO.output(int(self.gpio), 0) | |||
| def off(self): | |||
| GPIO.output(int(self.gpio), 1) | |||
| @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" | |||
| @@ -1,40 +0,0 @@ | |||
| from modules import cbpi | |||
| from modules.core.controller import KettleController | |||
| from modules.core.props import Property | |||
| @cbpi.controller | |||
| class Hysteresis(KettleController): | |||
| # Custom Properties | |||
| 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): | |||
| ''' | |||
| Invoked when the automatic is stopped. | |||
| Normally you switch off the actors and clean up everything | |||
| :return: None | |||
| ''' | |||
| super(KettleController, self).stop() | |||
| self.heater_off() | |||
| def run(self): | |||
| ''' | |||
| Each controller is exectuted in its own thread. The run method is the entry point | |||
| :return: | |||
| ''' | |||
| while self.is_running(): | |||
| if self.get_temp() < self.get_target_temp() - float(self.on): | |||
| self.heater_on(100) | |||
| elif self.get_temp() >= self.get_target_temp() - float(self.off): | |||
| self.heater_off() | |||
| else: | |||
| self.heater_off() | |||
| self.sleep(1) | |||
| @@ -1,118 +0,0 @@ | |||
| # -*- coding: utf-8 -*- | |||
| import os | |||
| from subprocess import Popen, PIPE, call | |||
| from modules import cbpi, app | |||
| from modules.core.hardware import SensorPassive | |||
| import json | |||
| import os, re, threading, time | |||
| from flask import Blueprint, render_template, request | |||
| from modules.core.props import Property | |||
| blueprint = Blueprint('one_wire', __name__) | |||
| temp = 22 | |||
| def getSensors(): | |||
| try: | |||
| arr = [] | |||
| for dirname in os.listdir('/sys/bus/w1/devices'): | |||
| if (dirname.startswith("28") or dirname.startswith("10")): | |||
| cbpi.app.logger.info("Device %s Found (Family: 28/10, Thermometer on GPIO4 (w1))" % dirname) | |||
| arr.append(dirname) | |||
| return arr | |||
| except: | |||
| return [] | |||
| class myThread (threading.Thread): | |||
| value = 0 | |||
| def __init__(self, sensor_name): | |||
| threading.Thread.__init__(self) | |||
| self.value = 0 | |||
| self.sensor_name = sensor_name | |||
| self.runnig = True | |||
| def shutdown(self): | |||
| pass | |||
| def stop(self): | |||
| self.runnig = False | |||
| def run(self): | |||
| while self.runnig: | |||
| try: | |||
| app.logger.info("READ TEMP") | |||
| ## Test Mode | |||
| if self.sensor_name is None: | |||
| return | |||
| with open('/sys/bus/w1/devices/w1_bus_master1/%s/w1_slave' % self.sensor_name, 'r') as content_file: | |||
| content = content_file.read() | |||
| if (content.split('\n')[0].split(' ')[11] == "YES"): | |||
| temp = float(content.split("=")[-1]) / 1000 # temp in Celcius | |||
| self.value = temp | |||
| except: | |||
| pass | |||
| time.sleep(4) | |||
| @cbpi.sensor | |||
| class ONE_WIRE_SENSOR(SensorPassive): | |||
| 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): | |||
| self.t = myThread(self.sensor_name) | |||
| def shudown(): | |||
| shudown.cb.shutdown() | |||
| shudown.cb = self.t | |||
| self.t.start() | |||
| def stop(self): | |||
| try: | |||
| self.t.stop() | |||
| except: | |||
| pass | |||
| def read(self): | |||
| if self.get_config_parameter("unit", "C") == "C": | |||
| self.data_received(round(self.t.value + self.offset_value(), 2)) | |||
| else: | |||
| 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: | |||
| call(["modprobe", "w1-gpio"]) | |||
| call(["modprobe", "w1-therm"]) | |||
| except Exception as e: | |||
| pass | |||
| @blueprint.route('/<int:t>', methods=['GET']) | |||
| def set_temp(t): | |||
| global temp | |||
| temp = t | |||
| return ('', 204) | |||
| @cbpi.initalizer() | |||
| def init(cbpi): | |||
| cbpi.app.register_blueprint(blueprint, url_prefix='/api/one_wire') | |||
| @@ -0,0 +1,36 @@ | |||
| # -*- coding: utf-8 -*- | |||
| from modules.core.basetypes import Actor, Sensor | |||
| from modules.core.core import cbpi | |||
| from modules.core.proptypes import Property | |||
| import random | |||
| print "INit SENSOR" | |||
| @cbpi.addon.sensor.type("Dummy Sensor") | |||
| class Dummy(Sensor): | |||
| text = Property.Text(label="Text", required=True, description="This is a parameter", configurable=True) | |||
| p = Property.Select(label="hallo",options=[1,2,3]) | |||
| def init(self): | |||
| if self.api.get_config_parameter("unit","C") == "C": | |||
| self.unit = "°C" | |||
| else: | |||
| self.unit = "°F" | |||
| @cbpi.addon.sensor.action("WOHOO") | |||
| def myaction(self): | |||
| print self.text | |||
| print "SENSOR ACTION HALLO!!!" | |||
| def execute(self): | |||
| while True: | |||
| try: | |||
| self.update_value(int(self.text)) | |||
| except: | |||
| pass | |||
| self.api.sleep(1) | |||
| @cbpi.addon.core.action(key="clear", label="Clear all Logs") | |||
| def woohoo(cbpi): | |||
| print "COOL" | |||
| cbpi.notify(headline="HELLO WORLD",message="") | |||
| @@ -0,0 +1,14 @@ | |||
| from modules.core.basetypes import Step | |||
| from modules.core.core import cbpi | |||
| from modules.core.proptypes import Property | |||
| @cbpi.addon.step.type("Dummy Step") | |||
| class Dummy(Step): | |||
| text = Property.Text(label="Text", configurable=True, description="WOHOOO") | |||
| def execute(self): | |||
| #print self.text | |||
| pass | |||
| @@ -1,50 +1,53 @@ | |||
| import time | |||
| from thread import start_new_thread | |||
| from modules import cbpi | |||
| try: | |||
| import RPi.GPIO as GPIO | |||
| except Exception as e: | |||
| pass | |||
| class Buzzer(object): | |||
| sound = ["H", 0.1, "L", 0.1, "H", 0.1, "L", 0.1, "H", 0.1, "L"] | |||
| def __init__(self, gpio): | |||
| try: | |||
| cbpi.app.logger.info("INIT BUZZER NOW GPIO%s" % gpio) | |||
| self.gpio = int(gpio) | |||
| GPIO.setmode(GPIO.BCM) | |||
| GPIO.setup(self.gpio, GPIO.OUT) | |||
| self.state = True | |||
| cbpi.app.logger.info("BUZZER SETUP OK") | |||
| except Exception as e: | |||
| cbpi.app.logger.info("BUZZER EXCEPTION %s" % str(e)) | |||
| self.state = False | |||
| def beep(self): | |||
| if self.state is False: | |||
| cbpi.app.logger.error("BUZZER not working") | |||
| return | |||
| def play(sound): | |||
| try: | |||
| for i in sound: | |||
| if (isinstance(i, str)): | |||
| if i == "H": | |||
| GPIO.output(int(self.gpio), GPIO.HIGH) | |||
| else: | |||
| GPIO.output(int(self.gpio), GPIO.LOW) | |||
| else: | |||
| time.sleep(i) | |||
| except Exception as e: | |||
| pass | |||
| start_new_thread(play, (self.sound,)) | |||
| @cbpi.initalizer(order=1) | |||
| def init(cbpi): | |||
| gpio = cbpi.get_config_parameter("buzzer", 16) | |||
| cbpi.buzzer = Buzzer(gpio) | |||
| cbpi.beep() | |||
| cbpi.app.logger.info("INIT OK") | |||
| import time | |||
| from thread import start_new_thread | |||
| from modules.core.baseapi import Buzzer | |||
| from modules.core.core import cbpi | |||
| try: | |||
| import RPi.GPIO as GPIO | |||
| except Exception as e: | |||
| pass | |||
| class GPIOBuzzer(Buzzer): | |||
| sound = ["H", 0.1, "L", 0.1, "H", 0.1, "L", 0.1, "H", 0.1, "L"] | |||
| def __init__(self, gpio): | |||
| try: | |||
| cbpi._app.logger.info("INIT BUZZER NOW GPIO%s" % gpio) | |||
| self.gpio = int(gpio) | |||
| GPIO.setmode(GPIO.BCM) | |||
| GPIO.setup(self.gpio, GPIO.OUT) | |||
| self.state = True | |||
| cbpi._app.logger.info("BUZZER SETUP OK") | |||
| except Exception as e: | |||
| cbpi._app.logger.info("BUZZER EXCEPTION %s" % str(e)) | |||
| self.state = False | |||
| def beep(self): | |||
| if self.state is False: | |||
| cbpi._app.logger.error("BUZZER not working") | |||
| return | |||
| def play(sound): | |||
| try: | |||
| for i in sound: | |||
| if (isinstance(i, str)): | |||
| if i == "H": | |||
| GPIO.output(int(self.gpio), GPIO.HIGH) | |||
| else: | |||
| GPIO.output(int(self.gpio), GPIO.LOW) | |||
| else: | |||
| time.sleep(i) | |||
| except Exception as e: | |||
| pass | |||
| start_new_thread(play, (self.sound,)) | |||
| @cbpi.addon.core.initializer(order=1) | |||
| def init(cbpi): | |||
| gpio = cbpi.get_config_parameter("buzzer", 16) | |||
| cbpi.buzzer = GPIOBuzzer(gpio) | |||
| @@ -1,57 +1,89 @@ | |||
| import time | |||
| from flask import json, request | |||
| from flask_classy import route | |||
| from modules import DBModel, cbpi, get_db | |||
| from modules.core.baseview import BaseView | |||
| class Config(DBModel): | |||
| __fields__ = ["type", "value", "description", "options"] | |||
| __table_name__ = "config" | |||
| __json_fields__ = ["options"] | |||
| __priamry_key__ = "name" | |||
| class ConfigView(BaseView): | |||
| model = Config | |||
| cache_key = "config" | |||
| @route('/<name>', methods=["PUT"]) | |||
| def put(self, name): | |||
| data = request.json | |||
| data["name"] = name | |||
| update_data = {"name": data["name"], "value": data["value"]} | |||
| if self.api.cache.get(self.cache_key) is not None: | |||
| 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]) | |||
| return json.dumps(self.api.cache.get(self.cache_key)[name].__dict__) | |||
| @route('/<id>', methods=["GET"]) | |||
| def getOne(self, id): | |||
| return ('NOT SUPPORTED', 400) | |||
| @route('/<id>', methods=["DELETE"]) | |||
| def delete(self, id): | |||
| return ('NOT SUPPORTED', 400) | |||
| @route('/', methods=["POST"]) | |||
| def post(self): | |||
| return ('NOT SUPPORTED', 400) | |||
| @classmethod | |||
| def init_cache(cls): | |||
| with cls.api.app.app_context(): | |||
| cls.api.cache[cls.cache_key] = {} | |||
| for key, value in cls.model.get_all().iteritems(): | |||
| cls.post_init_callback(value) | |||
| cls.api.cache[cls.cache_key][value.name] = value | |||
| @cbpi.initalizer(order=0) | |||
| def init(cbpi): | |||
| ConfigView.register(cbpi.app, route_base='/api/config') | |||
| ConfigView.init_cache() | |||
| import time | |||
| from flask import json, request | |||
| from flask_classy import route | |||
| from modules.core.core import cbpi | |||
| from modules.core.db import DBModel | |||
| from modules.core.baseview import BaseView | |||
| from modules.database.dbmodel import Config | |||
| class ConfigView(BaseView): | |||
| model = Config | |||
| cache_key = "config" | |||
| @route('/<name>', methods=["PUT"]) | |||
| def put(self, name): | |||
| """ | |||
| Set new config value | |||
| --- | |||
| tags: | |||
| - config | |||
| responses: | |||
| 204: | |||
| description: New config value set | |||
| """ | |||
| data = request.json | |||
| data["name"] = name | |||
| update_data = {"name": data["name"], "value": data["value"]} | |||
| if self.api.cache.get(self.cache_key) is not None: | |||
| 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.api.emit("CONFIG_UPDATE", name=name, data=data["value"]) | |||
| return json.dumps(self.api.cache.get(self.cache_key)[name].__dict__) | |||
| @route('/<id>', methods=["GET"]) | |||
| def getOne(self, id): | |||
| """ | |||
| Get config parameter | |||
| --- | |||
| tags: | |||
| - config | |||
| responses: | |||
| 400: | |||
| description: Get one config parameter via web api is not supported | |||
| """ | |||
| return ('NOT SUPPORTED', 400) | |||
| @route('/<id>', methods=["DELETE"]) | |||
| def delete(self, id): | |||
| """ | |||
| Delete config parameter | |||
| --- | |||
| tags: | |||
| - config | |||
| responses: | |||
| 400: | |||
| description: Deleting config parameter via web api is not supported | |||
| """ | |||
| return ('NOT SUPPORTED', 400) | |||
| @route('/', methods=["POST"]) | |||
| def post(self): | |||
| """ | |||
| Get config parameter | |||
| --- | |||
| tags: | |||
| - config | |||
| responses: | |||
| 400: | |||
| description: Adding new config parameter via web api is not supported | |||
| """ | |||
| return ('NOT SUPPORTED', 400) | |||
| @classmethod | |||
| def init_cache(cls): | |||
| with cls.api._app.app_context(): | |||
| cls.api.cache[cls.cache_key] = {} | |||
| for key, value in cls.model.get_all().iteritems(): | |||
| cls.post_init_callback(value) | |||
| cls.api.cache[cls.cache_key][value.name] = value | |||
| @cbpi.addon.core.initializer(order=0) | |||
| def init(cbpi): | |||
| ConfigView.register(cbpi._app, route_base='/api/config') | |||
| ConfigView.init_cache() | |||
| @@ -0,0 +1,216 @@ | |||
| from proptypes import * | |||
| class BaseAPI(object): | |||
| def __init__(self, cbpi): | |||
| self.cbpi = cbpi | |||
| self.cbpi.cache[self.key] = {} | |||
| def init(self): | |||
| for name, value in self.cbpi.cache[self.key].iteritems(): | |||
| value["class"].init_global() | |||
| def parseProps(self, key, cls, **options): | |||
| name = cls.__name__ | |||
| tmpObj = cls() | |||
| try: | |||
| doc = tmpObj.__doc__.strip() | |||
| except: | |||
| doc = "" | |||
| self.cbpi.cache.get(key)[name] = {"name": name, "class": cls, "description":doc, "properties": [], "actions": []} | |||
| self.cbpi.cache.get(key)[name].update(options) | |||
| members = [attr for attr in dir(tmpObj) if not callable(getattr(tmpObj, attr)) and not attr.startswith("__")] | |||
| for m in members: | |||
| t = tmpObj.__getattribute__(m) | |||
| if isinstance(t, Property.Number): | |||
| self.cbpi.cache.get(key)[name]["properties"].append({"name": m, "label": t.label, "type": "number", "configurable": t.configurable, "description": t.description, "default_value": t.default_value}) | |||
| elif isinstance(t, Property.Text): | |||
| self.cbpi.cache.get(key)[name]["properties"].append({"name": m, "label": t.label, "type": "text", "required": t.required, "configurable": t.configurable, "description": t.description, "default_value": t.default_value}) | |||
| elif isinstance(tmpObj.__getattribute__(m), Property.Select): | |||
| self.cbpi.cache.get(key)[name]["properties"].append({"name": m, "label": t.label, "type": "select", "configurable": True, "options": t.options, "description": t.description}) | |||
| elif isinstance(tmpObj.__getattribute__(m), Property.Actor): | |||
| self.cbpi.cache.get(key)[name]["properties"].append({"name": m, "label": t.label, "type": "actor", "configurable": True, "description": t.description}) | |||
| elif isinstance(tmpObj.__getattribute__(m), Property.Sensor): | |||
| self.cbpi.cache.get(key)[name]["properties"].append({"name": m, "label": t.label, "type": "sensor", "configurable": True, "description": t.description}) | |||
| elif isinstance(tmpObj.__getattribute__(m), Property.Kettle): | |||
| self.cbpi.cache.get(key)[name]["properties"].append({"name": m, "label": t.label, "type": "kettle", "configurable": True, "description": t.description}) | |||
| for method_name, method in cls.__dict__.iteritems(): | |||
| if hasattr(method, "action"): | |||
| label = method.__getattribute__("label") | |||
| self.cbpi.cache.get(key)[name]["actions"].append({"method": method_name, "label": label}) | |||
| return cls | |||
| class SensorAPI(BaseAPI): | |||
| key = "sensor_types" | |||
| def type(self, description="Step", **options): | |||
| def decorator(f): | |||
| BaseAPI.parseProps(self, self.key,f, description=description) | |||
| return f | |||
| return decorator | |||
| def action(self, label): | |||
| def real_decorator(func): | |||
| func.action = True | |||
| func.label = label | |||
| return func | |||
| return real_decorator | |||
| class StepAPI(BaseAPI): | |||
| key = "step_types" | |||
| def init(self): | |||
| pass | |||
| def type(self, description="Step", **options): | |||
| def decorator(f): | |||
| BaseAPI.parseProps(self, self.key,f, description=description) | |||
| return f | |||
| return decorator | |||
| def action(self, label): | |||
| def real_decorator(func): | |||
| func.action = True | |||
| func.label = label | |||
| return func | |||
| return real_decorator | |||
| class ActorAPI(BaseAPI): | |||
| key = "actor_types" | |||
| def type(self, description="", **options): | |||
| def decorator(f): | |||
| BaseAPI.parseProps(self, self.key, f, description=description) | |||
| return f | |||
| return decorator | |||
| def action(self, label): | |||
| def real_decorator(func): | |||
| func.action = True | |||
| func.label = label | |||
| return func | |||
| return real_decorator | |||
| class KettleAPI(BaseAPI): | |||
| key = "controller_types" | |||
| def controller(self, description="", **options): | |||
| def decorator(f): | |||
| BaseAPI.parseProps(self, self.key,f,description=description) | |||
| return f | |||
| return decorator | |||
| def action(self, label): | |||
| def real_decorator(func): | |||
| func.action = True | |||
| func.label = label | |||
| return func | |||
| return real_decorator | |||
| class FermenterAPI(BaseAPI): | |||
| key = "fermentation_controller_types" | |||
| def controller(self, description="Step", **options): | |||
| def decorator(f): | |||
| BaseAPI.parseProps(self, self.key,f,description=description) | |||
| return f | |||
| return decorator | |||
| def action(self, label): | |||
| def real_decorator(func): | |||
| func.action = True | |||
| func.label = label | |||
| return func | |||
| return real_decorator | |||
| class CoreAPI(BaseAPI): | |||
| key = "core" | |||
| def __init__(self, cbpi): | |||
| self.cbpi = cbpi | |||
| self.cbpi.cache["actions"] = {} | |||
| self.cbpi.cache["init"] = [] | |||
| self.cbpi.cache["js"] = {} | |||
| self.cbpi.cache["background"] = [] | |||
| def init(self): | |||
| self.cbpi.cache["init"] = sorted(self.cbpi.cache["init"], key=lambda k: k['order']) | |||
| for value in self.cbpi.cache.get("init"): | |||
| print value | |||
| value["function"](self.cbpi) | |||
| def job(interval, method): | |||
| while True: | |||
| try: | |||
| method(self.cbpi) | |||
| except Exception as e: | |||
| print e | |||
| self.cbpi._socketio.sleep(interval) | |||
| for value in self.cbpi.cache.get("background"): | |||
| t = self.cbpi._socketio.start_background_task(target=job, interval=value.get("interval"), method=value.get("function")) | |||
| def add_js(self, name, file): | |||
| self.cbpi.cache["js"][name] = file | |||
| def initializer(self, order=0, **options): | |||
| def decorator(f): | |||
| self.cbpi.cache.get("init").append({"function": f, "order": order}) | |||
| return f | |||
| return decorator | |||
| def action(self, key, label, **options): | |||
| def decorator(f): | |||
| self.cbpi.cache.get("actions")[key] = {"label": label, "function": f} | |||
| return f | |||
| return decorator | |||
| def backgroundjob(self, key, interval, **options): | |||
| def decorator(f): | |||
| self.cbpi.cache.get("background").append({"function": f, "key": key, "interval": interval}) | |||
| return f | |||
| return decorator | |||
| def listen(self, name, method=None, async=False): | |||
| if method is not None: | |||
| if self.cbpi.eventbus.get(name) is None: | |||
| self.cbpi.eventbus[name] = [] | |||
| self.cbpi.eventbus[name].append({"function": method, "async": async}) | |||
| else: | |||
| def real_decorator(function): | |||
| if self.cbpi.eventbus.get(name) is None: | |||
| self.cbpi.eventbus[name] = [] | |||
| self.cbpi.eventbus[name].append({"function": function, "async": async}) | |||
| def wrapper(*args, **kwargs): | |||
| return function(*args, **kwargs) | |||
| return wrapper | |||
| return real_decorator | |||
| class Buzzer(object): | |||
| def beep(): | |||
| pass | |||
| @@ -1,168 +1,249 @@ | |||
| from modules import cbpi | |||
| class ActorController(object): | |||
| @cbpi.try_catch(None) | |||
| def actor_on(self, power=100, id=None): | |||
| if id is None: | |||
| id = self.heater | |||
| self.api.switch_actor_on(int(id), power=power) | |||
| @cbpi.try_catch(None) | |||
| def actor_off(self, id=None): | |||
| if id is None: | |||
| id = self.heater | |||
| self.api.switch_actor_off(int(id)) | |||
| @cbpi.try_catch(None) | |||
| def actor_power(self, power, id=None): | |||
| if id is None: | |||
| id = self.heater | |||
| self.api.actor_power(int(id), power) | |||
| class SensorController(object): | |||
| @cbpi.try_catch(None) | |||
| def get_sensor_value(self, id=None): | |||
| if id is None: | |||
| id = self.sensor | |||
| return cbpi.get_sensor_value(id) | |||
| class ControllerBase(object): | |||
| __dirty = False | |||
| __running = False | |||
| @staticmethod | |||
| def init_global(): | |||
| print "GLOBAL CONTROLLER INIT" | |||
| def notify(self, headline, message, type="success", timeout=5000): | |||
| self.api.notify(headline, message, type, timeout) | |||
| def is_running(self): | |||
| return self.__running | |||
| def init(self): | |||
| self.__running = True | |||
| def sleep(self, seconds): | |||
| self.api.socketio.sleep(seconds) | |||
| def stop(self): | |||
| self.__running = False | |||
| def __init__(self, *args, **kwds): | |||
| for a in kwds: | |||
| super(ControllerBase, self).__setattr__(a, kwds.get(a)) | |||
| self.api = kwds.get("api") | |||
| self.heater = kwds.get("heater") | |||
| self.sensor = kwds.get("sensor") | |||
| def run(self): | |||
| pass | |||
| class KettleController(ControllerBase, ActorController, SensorController): | |||
| @staticmethod | |||
| def chart(kettle): | |||
| result = [] | |||
| result.append({"name": "Temp", "data_type": "sensor", "data_id": kettle.sensor}) | |||
| result.append({"name": "Target Temp", "data_type": "kettle", "data_id": kettle.id}) | |||
| return result | |||
| def __init__(self, *args, **kwds): | |||
| ControllerBase.__init__(self, *args, **kwds) | |||
| self.kettle_id = kwds.get("kettle_id") | |||
| @cbpi.try_catch(None) | |||
| def heater_on(self, power=100): | |||
| k = self.api.cache.get("kettle").get(self.kettle_id) | |||
| if k.heater is not None: | |||
| self.actor_on(power, int(k.heater)) | |||
| @cbpi.try_catch(None) | |||
| def heater_off(self): | |||
| k = self.api.cache.get("kettle").get(self.kettle_id) | |||
| if k.heater is not None: | |||
| self.actor_off(int(k.heater)) | |||
| @cbpi.try_catch(None) | |||
| def get_temp(self, id=None): | |||
| if id is None: | |||
| id = self.kettle_id | |||
| return self.get_sensor_value(int(self.api.cache.get("kettle").get(id).sensor)) | |||
| @cbpi.try_catch(None) | |||
| def get_target_temp(self, id=None): | |||
| if id is None: | |||
| id = self.kettle_id | |||
| return self.api.cache.get("kettle").get(id).target_temp | |||
| class FermenterController(ControllerBase, ActorController, SensorController): | |||
| @staticmethod | |||
| def chart(fermenter): | |||
| result = [] | |||
| result.append({"name": "Temp", "data_type": "sensor", "data_id": fermenter.sensor}) | |||
| result.append({"name": "Target Temp", "data_type": "fermenter", "data_id": fermenter.id}) | |||
| return result | |||
| def __init__(self, *args, **kwds): | |||
| ControllerBase.__init__(self, *args, **kwds) | |||
| self.fermenter_id = kwds.get("fermenter_id") | |||
| self.cooler = kwds.get("cooler") | |||
| @cbpi.try_catch(None) | |||
| def get_target_temp(self, id=None): | |||
| if id is None: | |||
| id = self.fermenter_id | |||
| return self.api.cache.get("fermenter").get(id).target_temp | |||
| @cbpi.try_catch(None) | |||
| def heater_on(self, power=100): | |||
| f = self.api.cache.get("fermenter").get(self.fermenter_id) | |||
| if f.heater is not None: | |||
| self.actor_on(int(f.heater)) | |||
| @cbpi.try_catch(None) | |||
| def heater_off(self): | |||
| f = self.api.cache.get("fermenter").get(self.fermenter_id) | |||
| if f.heater is not None: | |||
| self.actor_off(int(f.heater)) | |||
| @cbpi.try_catch(None) | |||
| def cooler_on(self, power=100): | |||
| f = self.api.cache.get("fermenter").get(self.fermenter_id) | |||
| 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.cooler is not None: | |||
| self.actor_off(int(f.cooler)) | |||
| @cbpi.try_catch(None) | |||
| def get_temp(self, id=None): | |||
| if id is None: | |||
| id = self.fermenter_id | |||
| return self.get_sensor_value(int(self.api.cache.get("fermenter").get(id).sensor)) | |||
| class Base(object): | |||
| def __init__(self, *args, **kwds): | |||
| for a in kwds: | |||
| super(Base, self).__setattr__(a, kwds.get(a)) | |||
| self.api = kwds.get("cbpi") | |||
| self.id = kwds.get("id") | |||
| self.value = None | |||
| self.__dirty = False | |||
| class Actor(Base): | |||
| @classmethod | |||
| def init_global(cls): | |||
| print "GLOBAL INIT ACTOR" | |||
| pass | |||
| def init(self): | |||
| pass | |||
| def shutdown(self): | |||
| pass | |||
| def on(self, power=100): | |||
| print "SWITCH ON" | |||
| pass | |||
| def off(self): | |||
| print "SWITCH OFF" | |||
| pass | |||
| def power(self, power): | |||
| print "SET POWER", power | |||
| pass | |||
| def state(self): | |||
| pass | |||
| class Sensor(Base): | |||
| unit = "" | |||
| @classmethod | |||
| def init_global(cls): | |||
| pass | |||
| def init(self): | |||
| pass | |||
| def get_unit(self): | |||
| pass | |||
| def stop(self): | |||
| pass | |||
| def update_value(self, value): | |||
| self.value = value | |||
| self.cbpi.sensor.write_log(self.id, value) | |||
| self.cbpi.emit("SENSOR_UPDATE", id=self.id, value=value) | |||
| self.cbpi.ws_emit("SENSOR_UPDATE", self.cbpi.cache["sensors"][self.id]) | |||
| def execute(self): | |||
| print "EXECUTE" | |||
| pass | |||
| class ControllerBase(object): | |||
| __dirty = False | |||
| __running = False | |||
| @staticmethod | |||
| def init_global(): | |||
| print "GLOBAL CONTROLLER INIT" | |||
| def notify(self, headline, message, type="success", timeout=5000): | |||
| self.api.notify(headline, message, type, timeout) | |||
| def is_running(self): | |||
| return self.__running | |||
| def init(self): | |||
| self.__running = True | |||
| def sleep(self, seconds): | |||
| self.api.sleep(seconds) | |||
| def stop(self): | |||
| self.__running = False | |||
| def get_sensor_value(self, id): | |||
| return self.api.sensor.get_sensor_value(id) | |||
| def __init__(self, *args, **kwds): | |||
| for a in kwds: | |||
| super(ControllerBase, self).__setattr__(a, kwds.get(a)) | |||
| self.api = kwds.get("api") | |||
| self.heater = kwds.get("heater") | |||
| self.sensor = kwds.get("sensor") | |||
| def actor_on(self,id, power=100): | |||
| self.api.actor.on(id, power=power) | |||
| def actor_off(self, id): | |||
| self.api.actor.off(id) | |||
| def actor_power(self, power, id=None): | |||
| self.api.actor.power(id, power) | |||
| def run(self): | |||
| pass | |||
| class KettleController(ControllerBase): | |||
| @staticmethod | |||
| def chart(kettle): | |||
| result = [] | |||
| result.append({"name": "Temp", "data_type": "sensor", "data_id": kettle.sensor}) | |||
| result.append({"name": "Target Temp", "data_type": "kettle", "data_id": kettle.id}) | |||
| return result | |||
| def __init__(self, *args, **kwds): | |||
| ControllerBase.__init__(self, *args, **kwds) | |||
| self.kettle_id = kwds.get("kettle_id") | |||
| def heater_on(self, power=100): | |||
| k = self.api.cache.get("kettle").get(self.kettle_id) | |||
| if k.heater is not None: | |||
| self.actor_on(k.heater, power) | |||
| def heater_off(self): | |||
| k = self.api.cache.get("kettle").get(self.kettle_id) | |||
| if k.heater is not None: | |||
| self.actor_off(k.heater) | |||
| def get_temp(self, id=None): | |||
| if id is None: | |||
| id = self.kettle_id | |||
| return self.get_sensor_value(int(self.api.cache.get("kettle").get(id).sensor)) | |||
| def get_target_temp(self, id=None): | |||
| if id is None: | |||
| id = self.kettle_id | |||
| return self.api.cache.get("kettle").get(id).target_temp | |||
| class FermenterController(ControllerBase): | |||
| @staticmethod | |||
| def chart(fermenter): | |||
| result = [] | |||
| result.append({"name": "Temp", "data_type": "sensor", "data_id": fermenter.sensor}) | |||
| result.append({"name": "Target Temp", "data_type": "fermenter", "data_id": fermenter.id}) | |||
| return result | |||
| def __init__(self, *args, **kwds): | |||
| ControllerBase.__init__(self, *args, **kwds) | |||
| self.fermenter_id = kwds.get("fermenter_id") | |||
| self.cooler = kwds.get("cooler") | |||
| def get_target_temp(self, id=None): | |||
| if id is None: | |||
| id = self.fermenter_id | |||
| return self.api.cache.get("fermenter").get(id).target_temp | |||
| def heater_on(self, power=100): | |||
| f = self.api.cache.get("fermenter").get(self.fermenter_id) | |||
| if f.heater is not None: | |||
| self.actor_on(int(f.heater)) | |||
| def heater_off(self): | |||
| f = self.api.cache.get("fermenter").get(self.fermenter_id) | |||
| if f.heater is not None: | |||
| self.actor_off(int(f.heater)) | |||
| def cooler_on(self, power=100): | |||
| f = self.api.cache.get("fermenter").get(self.fermenter_id) | |||
| if f.cooler is not None: | |||
| self.actor_on(power, int(f.cooler)) | |||
| def cooler_off(self): | |||
| f = self.api.cache.get("fermenter").get(self.fermenter_id) | |||
| if f.cooler is not None: | |||
| self.actor_off(int(f.cooler)) | |||
| def get_temp(self, id=None): | |||
| if id is None: | |||
| id = self.fermenter_id | |||
| return self.get_sensor_value(int(self.api.cache.get("fermenter").get(id).sensor)) | |||
| class Step(Base): | |||
| @classmethod | |||
| def init_global(cls): | |||
| pass | |||
| __dirty = False | |||
| managed_fields = [] | |||
| n = False | |||
| def next(self): | |||
| self.n = True | |||
| def init(self): | |||
| pass | |||
| def finish(self): | |||
| pass | |||
| def reset(self): | |||
| pass | |||
| def execute(self): | |||
| print "-------------" | |||
| print "Step Info" | |||
| print "Kettle ID: %s" % self.kettle_id | |||
| print "ID: %s" % self.id | |||
| def __init__(self, *args, **kwds): | |||
| for a in kwds: | |||
| super(Step, self).__setattr__(a, kwds.get(a)) | |||
| self.api = kwds.get("api") | |||
| self.id = kwds.get("id") | |||
| self.name = kwds.get("name") | |||
| self.kettle_id = kwds.get("kettleid") | |||
| self.value = None | |||
| self.__dirty = False | |||
| def is_dirty(self): | |||
| return self.__dirty | |||
| def reset_dirty(self): | |||
| self.__dirty = False | |||
| def __setattr__(self, name, value): | |||
| if name != "_StepBase__dirty" and name in self.managed_fields: | |||
| self.__dirty = True | |||
| super(Step, self).__setattr__(name, value) | |||
| else: | |||
| super(Step, self).__setattr__(name, value) | |||
| @@ -1,109 +1,110 @@ | |||
| from flask import request, json | |||
| from flask_classy import route, FlaskView | |||
| from modules import cbpi | |||
| class BaseView(FlaskView): | |||
| as_array = False | |||
| cache_key = None | |||
| api = cbpi | |||
| @route('/<int:id>', methods=["GET"]) | |||
| def getOne(self, id): | |||
| if self.api.cache.get(self.cache_key) is not None: | |||
| return json.dumps(self.api.cache.get(self.cache_key).get(id)) | |||
| else: | |||
| return json.dumps(self.model.get_one(id)) | |||
| @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): | |||
| pass | |||
| def _post_post_callback(self, m): | |||
| pass | |||
| @route('/', methods=["POST"]) | |||
| def post(self): | |||
| data = request.json | |||
| 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) | |||
| return json.dumps(m) | |||
| def _pre_put_callback(self, m): | |||
| pass | |||
| def _post_put_callback(self, m): | |||
| pass | |||
| @route('/<int:id>', methods=["PUT"]) | |||
| def put(self, id): | |||
| data = request.json | |||
| data["id"] = id | |||
| try: | |||
| del data["instance"] | |||
| 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.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]) | |||
| return json.dumps(self.api.cache.get(self.cache_key)[id]) | |||
| else: | |||
| m = self.model.update(**data) | |||
| self._post_put_callback(m) | |||
| return json.dumps(m) | |||
| def _pre_delete_callback(self, m): | |||
| pass | |||
| 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]) | |||
| del self.api.cache.get(self.cache_key)[id] | |||
| m = self.model.delete(id) | |||
| def _post_delete_callback(self, id): | |||
| pass | |||
| return ('',204) | |||
| @classmethod | |||
| def post_init_callback(cls, obj): | |||
| pass | |||
| @classmethod | |||
| def init_cache(cls): | |||
| with cls.api.app.app_context(): | |||
| if cls.model.__as_array__ is True: | |||
| cls.api.cache[cls.cache_key] = [] | |||
| for value in cls.model.get_all(): | |||
| cls.post_init_callback(value) | |||
| cls.api.cache[cls.cache_key].append(value) | |||
| else: | |||
| cls.api.cache[cls.cache_key] = {} | |||
| for key, value in cls.model.get_all().iteritems(): | |||
| cls.post_init_callback(value) | |||
| cls.api.cache[cls.cache_key][key] = value | |||
| from flask import request, json | |||
| from flask_classy import route, FlaskView | |||
| from modules.core.core import cbpi | |||
| class BaseView(FlaskView): | |||
| as_array = False | |||
| cache_key = None | |||
| api = cbpi | |||
| @route('/<int:id>', methods=["GET"]) | |||
| def getOne(self, id): | |||
| if self.api.cache.get(self.cache_key) is not None: | |||
| return json.dumps(self.api.cache.get(self.cache_key).get(id)) | |||
| else: | |||
| return json.dumps(self.model.get_one(id)) | |||
| @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): | |||
| pass | |||
| def _post_post_callback(self, m): | |||
| pass | |||
| @route('/', methods=["POST"]) | |||
| def post(self): | |||
| data = request.json | |||
| self.api._app.logger.info("INSERT Model %s", self.model.__name__) | |||
| self._pre_post_callback(data) | |||
| m = self.model.insert(**data) | |||
| if self.api.cache.get(self.cache_key) is not None: | |||
| self.api.cache.get(self.cache_key)[m.id] = m | |||
| self._post_post_callback(m) | |||
| return json.dumps(m) | |||
| def _pre_put_callback(self, m): | |||
| pass | |||
| def _post_put_callback(self, m): | |||
| pass | |||
| @route('/<int:id>', methods=["PUT"]) | |||
| def put(self, id): | |||
| data = request.json | |||
| data["id"] = id | |||
| try: | |||
| del data["instance"] | |||
| 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.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]) | |||
| return json.dumps(self.api.cache.get(self.cache_key)[id]) | |||
| else: | |||
| m = self.model.update(**data) | |||
| self._post_put_callback(m) | |||
| return json.dumps(m) | |||
| def _pre_delete_callback(self, m): | |||
| pass | |||
| 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]) | |||
| del self.api.cache.get(self.cache_key)[id] | |||
| m = self.model.delete(id) | |||
| def _post_delete_callback(self, id): | |||
| pass | |||
| return ('',204) | |||
| @classmethod | |||
| def post_init_callback(cls, obj): | |||
| pass | |||
| @classmethod | |||
| def init_cache(cls): | |||
| with cls.api._app.app_context(): | |||
| if cls.model.__as_array__ is True: | |||
| cls.api.cache[cls.cache_key] = [] | |||
| for value in cls.model.get_all(): | |||
| cls.post_init_callback(value) | |||
| cls.api.cache[cls.cache_key].append(value) | |||
| else: | |||
| cls.api.cache[cls.cache_key] = {} | |||
| for key, value in cls.model.get_all().iteritems(): | |||
| cls.post_init_callback(value) | |||
| cls.api.cache[cls.cache_key][key] = value | |||
| @@ -1,494 +1,337 @@ | |||
| import inspect | |||
| import pprint | |||
| import sqlite3 | |||
| from flask import make_response, g | |||
| import datetime | |||
| from datetime import datetime | |||
| from flask.views import MethodView | |||
| from flask_classy import FlaskView, route | |||
| from time import localtime, strftime | |||
| from functools import wraps, update_wrapper | |||
| from props import * | |||
| from hardware import * | |||
| import time | |||
| import uuid | |||
| class NotificationAPI(object): | |||
| pass | |||
| class ActorAPI(object): | |||
| def init_actors(self): | |||
| 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"): | |||
| self.init_actor(key) | |||
| def init_actor(self, id): | |||
| try: | |||
| value = self.cache.get("actors").get(int(id)) | |||
| cfg = value.config.copy() | |||
| cfg.update(dict(api=self, id=id, name=value.name)) | |||
| cfg.update(dict(api=self, id=id, name=value.name)) | |||
| clazz = self.cache.get("actor_types").get(value.type).get("class") | |||
| value.instance = clazz(**cfg) | |||
| value.instance.init() | |||
| value.state = 0 | |||
| value.power = 100 | |||
| except Exception as e: | |||
| self.notify("Actor Error", "Failed to setup actor %s. Please check the configuraiton" % value.name, | |||
| type="danger", timeout=None) | |||
| self.app.logger.error("Initializing of Actor %s failed" % id) | |||
| def switch_actor_on(self, id, power=None): | |||
| actor = self.cache.get("actors").get(id) | |||
| if actor.state == 1: | |||
| return | |||
| actor.instance.on(power=power) | |||
| actor.state = 1 | |||
| if power is not None: | |||
| actor.power = power | |||
| self.emit("SWITCH_ACTOR", actor) | |||
| def actor_power(self, id, power=100): | |||
| actor = self.cache.get("actors").get(id) | |||
| actor.instance.set_power(power=power) | |||
| actor.power = power | |||
| self.emit("SWITCH_ACTOR", actor) | |||
| def switch_actor_off(self, id): | |||
| actor = self.cache.get("actors").get(id) | |||
| if actor.state == 0: | |||
| return | |||
| actor.instance.off() | |||
| actor.state = 0 | |||
| self.emit("SWITCH_ACTOR", actor) | |||
| class SensorAPI(object): | |||
| def init_sensors(self): | |||
| ''' | |||
| Initialize all sensors | |||
| :return: | |||
| ''' | |||
| self.app.logger.info("Init Sensors") | |||
| t = self.cache.get("sensor_types") | |||
| for key, value in t.iteritems(): | |||
| value.get("class").init_global() | |||
| for key in self.cache.get("sensors"): | |||
| self.init_sensor(key) | |||
| def stop_sensor(self, id): | |||
| try: | |||
| self.cache.get("sensors").get(id).instance.stop() | |||
| except Exception as e: | |||
| self.app.logger.info("Stop Sensor Error") | |||
| pass | |||
| def init_sensor(self, id): | |||
| ''' | |||
| initialize sensor by id | |||
| :param id: | |||
| :return: | |||
| ''' | |||
| def start_active_sensor(instance): | |||
| ''' | |||
| start active sensors as background job | |||
| :param instance: | |||
| :return: | |||
| ''' | |||
| instance.execute() | |||
| try: | |||
| if id in self.cache.get("sensor_instances"): | |||
| self.cache.get("sensor_instances").get(id).stop() | |||
| value = self.cache.get("sensors").get(id) | |||
| cfg = value.config.copy() | |||
| cfg.update(dict(api=self, id=id, name=value.name)) | |||
| clazz = self.cache.get("sensor_types").get(value.type).get("class") | |||
| value.instance = clazz(**cfg) | |||
| value.instance.init() | |||
| if isinstance(value.instance, SensorPassive): | |||
| # Passive Sensors | |||
| value.mode = "P" | |||
| else: | |||
| # Active Sensors | |||
| value.mode = "A" | |||
| t = self.socketio.start_background_task(target=start_active_sensor, instance=value.instance) | |||
| except Exception as e: | |||
| self.notify("Sensor Error", "Failed to setup Sensor %s. Please check the configuraiton" % value.name, type="danger", timeout=None) | |||
| self.app.logger.error("Initializing of Sensor %s failed" % id) | |||
| def receive_sensor_value(self, id, value): | |||
| self.emit("SENSOR_UPDATE", self.cache.get("sensors")[id]) | |||
| self.save_to_file(id, value) | |||
| def save_to_file(self, id, value, prefix="sensor"): | |||
| filename = "./logs/%s_%s.log" % (prefix, str(id)) | |||
| formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime()) | |||
| msg = str(formatted_time) + "," +str(value) + "\n" | |||
| with open(filename, "a") as file: | |||
| file.write(msg) | |||
| 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): | |||
| self.cache.get("sensors")[id].stop() | |||
| def get_sensor_value(self, id): | |||
| try: | |||
| id = int(id) | |||
| return float(self.cache.get("sensors")[id].instance.last_value) | |||
| except Exception as e: | |||
| return None | |||
| class CacheAPI(object): | |||
| def get_sensor(self, id): | |||
| try: | |||
| return self.cache["sensors"][id] | |||
| except: | |||
| return None | |||
| def get_actor(self, id): | |||
| try: | |||
| return self.cache["actors"][id] | |||
| except: | |||
| return None | |||
| class CraftBeerPi(ActorAPI, SensorAPI): | |||
| cache = { | |||
| "init": {}, | |||
| "config": {}, | |||
| "actor_types": {}, | |||
| "sensor_types": {}, | |||
| "sensors": {}, | |||
| "sensor_instances": {}, | |||
| "init": [], | |||
| "background":[], | |||
| "step_types": {}, | |||
| "controller_types": {}, | |||
| "messages": [], | |||
| "plugins": {}, | |||
| "fermentation_controller_types": {}, | |||
| "fermenter_task": {} | |||
| } | |||
| buzzer = None | |||
| eventbus = {} | |||
| # constructor | |||
| def __init__(self, app, socketio): | |||
| self.app = app | |||
| self.socketio = socketio | |||
| def emit(self, key, data): | |||
| self.socketio.emit(key, data, namespace='/brew') | |||
| def notify(self, headline, message, type="success", timeout=5000): | |||
| self.beep() | |||
| msg = {"id": str(uuid.uuid1()), "type": type, "headline": headline, "message": message, "timeout": timeout} | |||
| self.emit_message(msg) | |||
| def beep(self): | |||
| if self.buzzer is not None: | |||
| self.buzzer.beep() | |||
| def add_cache_callback(self, key, method): | |||
| method.callback = True | |||
| self.cache[key] = method | |||
| def get_config_parameter(self, key, default): | |||
| cfg = self.cache.get("config").get(key) | |||
| if cfg is None: | |||
| return default | |||
| 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(): | |||
| c = Config.insert(**{"name":name, "value": value, "type": type, "description": description, "options": options}) | |||
| if self.cache.get("config") is not None: | |||
| self.cache.get("config")[c.name] = c | |||
| def clear_cache(self, key, is_array=False): | |||
| if is_array: | |||
| self.cache[key] = [] | |||
| else: | |||
| self.cache[key] = {} | |||
| # helper method for parsing props | |||
| def __parseProps(self, key, cls): | |||
| name = cls.__name__ | |||
| 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, "description": t.description, "default_value": t.default_value}) | |||
| elif isinstance(tmpObj.__getattribute__(m), Property.Text): | |||
| t = tmpObj.__getattribute__(m) | |||
| self.cache[key][name]["properties"].append( | |||
| {"name": m, "label": t.label, "type": "text", "configurable": t.configurable, "default_value": t.default_value, "description": t.description}) | |||
| elif isinstance(tmpObj.__getattribute__(m), Property.Select): | |||
| t = tmpObj.__getattribute__(m) | |||
| self.cache[key][name]["properties"].append( | |||
| {"name": m, "label": t.label, "type": "select", "configurable": True, "options": t.options, "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) | |||
| def controller(self, cls): | |||
| return self.__parseProps("controller_types", cls) | |||
| def fermentation_controller(self, cls): | |||
| return self.__parseProps("fermentation_controller_types", cls) | |||
| def get_controller(self, name): | |||
| return self.cache["controller_types"].get(name) | |||
| def get_fermentation_controller(self, name): | |||
| return self.cache["fermentation_controller_types"].get(name) | |||
| # Step action | |||
| def action(self,label): | |||
| def real_decorator(func): | |||
| func.action = True | |||
| func.label = label | |||
| return func | |||
| return real_decorator | |||
| # step decorator | |||
| def step(self, cls): | |||
| key = "step_types" | |||
| name = cls.__name__ | |||
| 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), StepProperty.Number): | |||
| t = tmpObj.__getattribute__(m) | |||
| 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, "default_value": t.default_value, "description": t.description}) | |||
| elif isinstance(tmpObj.__getattribute__(m), StepProperty.Select): | |||
| t = tmpObj.__getattribute__(m) | |||
| self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "select", "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, "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, "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, "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 | |||
| # Event Bus | |||
| def event(self, name, async=False): | |||
| def real_decorator(function): | |||
| if self.eventbus.get(name) is None: | |||
| self.eventbus[name] = [] | |||
| self.eventbus[name].append({"function": function, "async": async}) | |||
| def wrapper(*args, **kwargs): | |||
| return function(*args, **kwargs) | |||
| return wrapper | |||
| return real_decorator | |||
| def emit_message(self, message): | |||
| self.emit_event(name="MESSAGE", message=message) | |||
| def emit_event(self, name, **kwargs): | |||
| for i in self.eventbus.get(name, []): | |||
| if i["async"] is False: | |||
| i["function"](**kwargs) | |||
| else: | |||
| t = self.socketio.start_background_task(target=i["function"], **kwargs) | |||
| # initializer decorator | |||
| def initalizer(self, order=0): | |||
| def real_decorator(function): | |||
| self.cache["init"].append({"function": function, "order": order}) | |||
| def wrapper(*args, **kwargs): | |||
| return function(*args, **kwargs) | |||
| return wrapper | |||
| return real_decorator | |||
| def try_catch(self, errorResult="ERROR"): | |||
| def real_decorator(function): | |||
| def wrapper(*args, **kwargs): | |||
| try: | |||
| return function(*args, **kwargs) | |||
| except: | |||
| self.app.logger.error("Exception in function %s. Return default %s" % (function.__name__, errorResult)) | |||
| return errorResult | |||
| return wrapper | |||
| return real_decorator | |||
| def nocache(self, view): | |||
| @wraps(view) | |||
| def no_cache(*args, **kwargs): | |||
| response = make_response(view(*args, **kwargs)) | |||
| response.headers['Last-Modified'] = datetime.now() | |||
| response.headers[ | |||
| 'Cache-Control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0' | |||
| response.headers['Pragma'] = 'no-cache' | |||
| response.headers['Expires'] = '-1' | |||
| return response | |||
| return update_wrapper(no_cache, view) | |||
| def init_kettle(self, id): | |||
| try: | |||
| value = self.cache.get("kettle").get(id) | |||
| value["state"] = False | |||
| except: | |||
| self.notify("Kettle Setup Faild", "Please check %s configuration" % value.name, type="danger", timeout=None) | |||
| self.app.logger.error("Initializing of Kettle %s failed" % id) | |||
| def run_init(self): | |||
| ''' | |||
| call all initialziers after startup | |||
| :return: | |||
| ''' | |||
| 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("INITIALIZER - METHOD %s PAHT %s: " % (i.get("function").__name__, str(inspect.getmodule(i.get("function")).__file__) )) | |||
| i.get("function")(self) | |||
| def backgroundtask(self, key, interval, config_parameter=None): | |||
| ''' | |||
| Background Task Decorator | |||
| :param key: | |||
| :param interval: | |||
| :param config_parameter: | |||
| :return: | |||
| ''' | |||
| def real_decorator(function): | |||
| self.cache["background"].append({"function": function, "key": key, "interval": interval, "config_parameter": config_parameter}) | |||
| def wrapper(*args, **kwargs): | |||
| return function(*args, **kwargs) | |||
| return wrapper | |||
| return real_decorator | |||
| def run_background_processes(self): | |||
| ''' | |||
| call all background task after startup | |||
| :return: | |||
| ''' | |||
| self.app.logger.info("Start Background") | |||
| def job(interval, method): | |||
| while True: | |||
| try: | |||
| method(self) | |||
| except Exception as e: | |||
| self.app.logger.error("Exception" + method.__name__ + ": " + str(e)) | |||
| self.socketio.sleep(interval) | |||
| for value in self.cache.get("background"): | |||
| t = self.socketio.start_background_task(target=job, interval=value.get("interval"), method=value.get("function")) | |||
| import json | |||
| import logging | |||
| import os | |||
| import sqlite3 | |||
| import uuid | |||
| from datetime import datetime | |||
| from functools import wraps, update_wrapper | |||
| from importlib import import_module | |||
| from time import localtime, strftime | |||
| from flask import Flask, redirect, json, g, make_response | |||
| from flask_socketio import SocketIO | |||
| from baseapi import * | |||
| from db import DBModel | |||
| from modules.core.basetypes import Sensor, Actor | |||
| from modules.database.dbmodel import Kettle | |||
| class ComplexEncoder(json.JSONEncoder): | |||
| def default(self, obj): | |||
| try: | |||
| if isinstance(obj, DBModel): | |||
| return obj.__dict__ | |||
| elif isinstance(obj, Actor): | |||
| return {"state": obj.value} | |||
| elif isinstance(obj, Sensor): | |||
| return {"value": obj.value, "unit": obj.unit} | |||
| elif hasattr(obj, "callback"): | |||
| return obj() | |||
| else: | |||
| return None | |||
| return None | |||
| except TypeError as e: | |||
| pass | |||
| return None | |||
| class Addon(object): | |||
| def __init__(self, cbpi): | |||
| self.step = StepAPI(cbpi) | |||
| self.actor = ActorAPI(cbpi) | |||
| self.sensor = SensorAPI(cbpi) | |||
| self.kettle = KettleAPI(cbpi) | |||
| self.fermenter = FermenterAPI(cbpi) | |||
| self.core = CoreAPI(cbpi) | |||
| def init(self): | |||
| self.core.init() | |||
| self.step.init() | |||
| self.actor.init() | |||
| self.sensor.init() | |||
| # self.kettle.init() | |||
| # self.fermenter.init() | |||
| class ActorCore(object): | |||
| key = "actor_types" | |||
| def __init__(self, cbpi): | |||
| self.cbpi = cbpi | |||
| self.cbpi.cache["actors"] = {} | |||
| self.cbpi.cache[self.key] = {} | |||
| def init(self): | |||
| for key, value in self.cbpi.cache["actors"].iteritems(): | |||
| self.init_one(key) | |||
| def init_one(self, id): | |||
| try: | |||
| actor = self.cbpi.cache["actors"][id] | |||
| clazz = self.cbpi.cache[self.key].get(actor.type)["class"] | |||
| cfg = actor.config.copy() | |||
| cfg.update(dict(cbpi=self.cbpi, id=id)) | |||
| self.cbpi.cache["actors"][id].instance = clazz(**cfg) | |||
| self.cbpi.emit("INIT_ACTOR", id=id) | |||
| except Exception as e: | |||
| print e | |||
| self.cbpi._app.logger.error(e) | |||
| def stop_one(self, id): | |||
| self.cbpi.cache["actors"][id]["instance"].stop() | |||
| self.cbpi.emit("STOP_ACTOR", id=id) | |||
| def on(self, id, power=100): | |||
| try: | |||
| actor = self.cbpi.cache["actors"].get(int(id)) | |||
| actor.instance.on() | |||
| actor.state = 1 | |||
| actor.power = power | |||
| self.cbpi.ws_emit("SWITCH_ACTOR", actor) | |||
| self.cbpi.emit("SWITCH_ACTOR_ON", id=id, power=power) | |||
| return True | |||
| except Exception as e: | |||
| print e | |||
| return False | |||
| def off(self, id): | |||
| try: | |||
| actor = self.cbpi.cache["actors"].get(int(id)) | |||
| actor.instance.off() | |||
| actor.state = 0 | |||
| self.cbpi.ws_emit("SWITCH_ACTOR", actor) | |||
| self.cbpi.emit("SWITCH_ACTOR_OFF", id=id) | |||
| return True | |||
| except Exception as e: | |||
| print e | |||
| return False | |||
| def power(self, id, power): | |||
| try: | |||
| actor = self.cbpi.cache["actors"].get(int(id)) | |||
| actor.instance.power(power) | |||
| actor.power = power | |||
| self.cbpi.ws_emit("SWITCH_ACTOR", actor) | |||
| self.cbpi.emit("SWITCH_ACTOR_POWER_CHANGE", id=id, power=power) | |||
| return True | |||
| except Exception as e: | |||
| print e | |||
| return False | |||
| def get_state(self, actor_id): | |||
| print actor_id | |||
| print self.cbpi | |||
| class SensorCore(object): | |||
| key = "sensor_types" | |||
| def __init__(self, cbpi): | |||
| self.cbpi = cbpi | |||
| self.cbpi.cache["sensors"] = {} | |||
| self.cbpi.cache["sensor_instances"] = {} | |||
| self.cbpi.cache["sensor_types"] = {} | |||
| def init(self): | |||
| for key, value in self.cbpi.cache["sensors"].iteritems(): | |||
| self.init_one(key) | |||
| def init_one(self, id): | |||
| try: | |||
| sensor = self.cbpi.cache["sensors"][id] | |||
| clazz = self.cbpi.cache[self.key].get(sensor.type)["class"] | |||
| cfg = sensor.config.copy() | |||
| cfg.update(dict(cbpi=self.cbpi, id=id)) | |||
| self.cbpi.cache["sensors"][id].instance = clazz(**cfg) | |||
| self.cbpi.cache["sensors"][id].instance.init() | |||
| print self.cbpi.cache["sensors"][id].instance | |||
| self.cbpi.emit("INIT_SENSOR", id=id) | |||
| def job(obj): | |||
| obj.execute() | |||
| t = self.cbpi._socketio.start_background_task(target=job, obj=self.cbpi.cache["sensors"][id].instance) | |||
| self.cbpi.emit("INIT_SENSOR", id=id) | |||
| except Exception as e: | |||
| print "ERROR" | |||
| self.cbpi._app.logger.error(e) | |||
| def stop_one(self, id): | |||
| print "OBJ", self.cbpi.cache["sensors"][id] | |||
| self.cbpi.cache["sensors"][id].instance.stop() | |||
| self.cbpi.emit("STOP_SENSOR", id=id) | |||
| def get_value(self, sensorid): | |||
| try: | |||
| return self.cbpi.cache["sensors"][sensorid].instance.value | |||
| except: | |||
| return None | |||
| def get_state(self, actor_id): | |||
| print actor_id | |||
| print self.cbpi | |||
| def write_log(self, id, value, prefix="sensor"): | |||
| filename = "./logs/%s_%s.log" % (prefix, str(id)) | |||
| formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime()) | |||
| msg = str(formatted_time) + "," + str(value) + "\n" | |||
| with open(filename, "a") as file: | |||
| file.write(msg) | |||
| class BrewingCore(object): | |||
| def __init__(self, cbpi): | |||
| self.cbpi = cbpi | |||
| self.cbpi.cache["step_types"] = {} | |||
| self.cbpi.cache["controller_types"] = {} | |||
| 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 get_controller(self, name): | |||
| return self.cbpi.cache["controller_types"].get(name) | |||
| def set_target_temp(self, id, temp): | |||
| self.cbpi.cache.get("kettle")[id].target_temp = float(temp) | |||
| Kettle.update(**self.cbpi.cache.get("kettle")[id].__dict__) | |||
| self.cbpi.ws_emit("UPDATE_KETTLE_TARGET_TEMP", {"id": id, "target_temp": temp}) | |||
| self.cbpi.emit("SET_KETTLE_TARGET_TEMP", id=id, temp=temp) | |||
| class FermentationCore(object): | |||
| def __init__(self, cbpi): | |||
| self.cbpi = cbpi | |||
| self.cbpi.cache["fermenter"] = {} | |||
| self.cbpi.cache["fermentation_controller_types"] = {} | |||
| def get_controller(self, name): | |||
| return self.cbpi.cache["fermentation_controller_types"].get(name) | |||
| class CraftBeerPI(object): | |||
| cache = {} | |||
| eventbus = {} | |||
| def __init__(self): | |||
| FORMAT = '%(asctime)-15s - %(levelname)s - %(message)s' | |||
| logging.basicConfig(filename='./logs/app.log', level=logging.INFO, format=FORMAT) | |||
| self.cache["messages"] = [] | |||
| self.modules = {} | |||
| self.cache["users"] = {'manuel': {'pw': 'secret'}} | |||
| self.addon = Addon(self) | |||
| self.actor = ActorCore(self) | |||
| self.sensor = SensorCore(self) | |||
| self.brewing = BrewingCore(self) | |||
| self.fermentation = FermentationCore(self) | |||
| self._app = Flask(__name__) | |||
| self._app.secret_key = 'Cr4ftB33rP1' | |||
| self._app.json_encoder = ComplexEncoder | |||
| self._socketio = SocketIO(self._app, json=json, logging=False) | |||
| @self._app.route('/') | |||
| def index(): | |||
| return redirect('ui') | |||
| def run(self): | |||
| self.__init_db() | |||
| self.loadPlugins() | |||
| self.addon.init() | |||
| self.sensor.init() | |||
| self.actor.init() | |||
| self.beep() | |||
| self._socketio.run(self._app, host='0.0.0.0', port=5000) | |||
| def beep(self): | |||
| self.buzzer.beep() | |||
| def sleep(self, seconds): | |||
| self._socketio.sleep(seconds) | |||
| def notify(self, headline, message, type="success", timeout=5000): | |||
| msg = {"id": str(uuid.uuid1()), "type": type, "headline": headline, "message": message, "timeout": timeout} | |||
| self.ws_emit("NOTIFY", msg) | |||
| def ws_emit(self, key, data): | |||
| self._socketio.emit(key, data, namespace='/brew') | |||
| def __init_db(self, ): | |||
| print "INIT DB" | |||
| with self._app.app_context(): | |||
| db = self.get_db() | |||
| try: | |||
| with self._app.open_resource('../../config/schema.sql', mode='r') as f: | |||
| db.cursor().executescript(f.read()) | |||
| db.commit() | |||
| except Exception as e: | |||
| print e | |||
| pass | |||
| def nocache(self, view): | |||
| @wraps(view) | |||
| def no_cache(*args, **kwargs): | |||
| response = make_response(view(*args, **kwargs)) | |||
| response.headers['Last-Modified'] = datetime.now() | |||
| response.headers[ | |||
| 'Cache-Control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0' | |||
| response.headers['Pragma'] = 'no-cache' | |||
| response.headers['Expires'] = '-1' | |||
| return response | |||
| return update_wrapper(no_cache, view) | |||
| def get_db(self): | |||
| db = getattr(g, '_database', None) | |||
| if db is None: | |||
| def dict_factory(cursor, row): | |||
| d = {} | |||
| for idx, col in enumerate(cursor.description): | |||
| d[col[0]] = row[idx] | |||
| return d | |||
| db = g._database = sqlite3.connect('craftbeerpi.db') | |||
| db.row_factory = dict_factory | |||
| return db | |||
| def add_cache_callback(self, key, method): | |||
| method.callback = True | |||
| self.cache[key] = method | |||
| def get_config_parameter(self, key, default): | |||
| cfg = self.cache["config"].get(key) | |||
| if cfg is None: | |||
| return default | |||
| else: | |||
| return cfg.value | |||
| def emit(self, key, **kwargs): | |||
| print key, kwargs | |||
| if self.eventbus.get(key) is not None: | |||
| for value in self.eventbus[key]: | |||
| if value["async"] is False: | |||
| value["function"](**kwargs) | |||
| else: | |||
| t = self.cbpi._socketio.start_background_task(target=value["function"], **kwargs) | |||
| def loadPlugins(self): | |||
| for filename in os.listdir("./modules/plugins"): | |||
| print filename | |||
| if os.path.isdir("./modules/plugins/" + filename) is False: | |||
| continue | |||
| try: | |||
| self.modules[filename] = import_module("modules.plugins.%s" % (filename)) | |||
| except Exception as e: | |||
| print e | |||
| self.notify("Failed to load plugin %s " % filename, str(e), type="danger", timeout=None) | |||
| cbpi = CraftBeerPI() | |||
| addon = cbpi.addon | |||
| @@ -1,134 +1,133 @@ | |||
| import sqlite3 | |||
| from flask import json, g | |||
| def get_db(): | |||
| db = getattr(g, '_database', None) | |||
| if db is None: | |||
| def dict_factory(cursor, row): | |||
| d = {} | |||
| for idx, col in enumerate(cursor.description): | |||
| d[col[0]] = row[idx] | |||
| return d | |||
| db = g._database = sqlite3.connect('craftbeerpi.db') | |||
| db.row_factory = dict_factory | |||
| return db | |||
| class DBModel(object): | |||
| __priamry_key__ = "id" | |||
| __as_array__ = False | |||
| __order_by__ = None | |||
| __json_fields__ = [] | |||
| def __init__(self, args): | |||
| self.__setattr__(self.__priamry_key__, args.get(self.__priamry_key__)) | |||
| for f in self.__fields__: | |||
| if f in self.__json_fields__: | |||
| if args.get(f) is not None: | |||
| if isinstance(args.get(f) , dict) or isinstance(args.get(f) , list) : | |||
| self.__setattr__(f, args.get(f)) | |||
| else: | |||
| self.__setattr__(f, json.loads(args.get(f))) | |||
| else: | |||
| self.__setattr__(f, None) | |||
| else: | |||
| self.__setattr__(f, args.get(f)) | |||
| @classmethod | |||
| def get_all(cls): | |||
| cur = get_db().cursor() | |||
| if cls.__order_by__ is not None: | |||
| 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 = {} | |||
| for r in cur.fetchall(): | |||
| result[r.get(cls.__priamry_key__)] = cls(r) | |||
| return result | |||
| @classmethod | |||
| def get_one(cls, id): | |||
| cur = get_db().cursor() | |||
| cur.execute("SELECT * FROM %s WHERE %s = ?" % (cls.__table_name__, cls.__priamry_key__), (id,)) | |||
| r = cur.fetchone() | |||
| if r is not None: | |||
| return cls(r) | |||
| else: | |||
| return None | |||
| @classmethod | |||
| def delete(cls, id): | |||
| cur = get_db().cursor() | |||
| cur.execute("DELETE FROM %s WHERE %s = ? " % (cls.__table_name__, cls.__priamry_key__), (id,)) | |||
| get_db().commit() | |||
| @classmethod | |||
| def insert(cls, **kwargs): | |||
| cur = get_db().cursor() | |||
| if cls.__priamry_key__ is not None and kwargs.has_key(cls.__priamry_key__): | |||
| query = "INSERT INTO %s (%s, %s) VALUES (?, %s)" % ( | |||
| cls.__table_name__, | |||
| cls.__priamry_key__, | |||
| ', '.join("'%s'" % str(x) for x in cls.__fields__), | |||
| ', '.join(['?'] * len(cls.__fields__))) | |||
| data = () | |||
| data = data + (kwargs.get(cls.__priamry_key__),) | |||
| for f in cls.__fields__: | |||
| if f in cls.__json_fields__: | |||
| data = data + (json.dumps(kwargs.get(f)),) | |||
| else: | |||
| data = data + (kwargs.get(f),) | |||
| else: | |||
| query = 'INSERT INTO %s (%s) VALUES (%s)' % ( | |||
| cls.__table_name__, | |||
| ', '.join("'%s'" % str(x) for x in cls.__fields__), | |||
| ', '.join(['?'] * len(cls.__fields__))) | |||
| data = () | |||
| for f in cls.__fields__: | |||
| if f in cls.__json_fields__: | |||
| data = data + (json.dumps(kwargs.get(f)),) | |||
| else: | |||
| data = data + (kwargs.get(f),) | |||
| cur.execute(query, data) | |||
| get_db().commit() | |||
| i = cur.lastrowid | |||
| kwargs["id"] = i | |||
| return cls(kwargs) | |||
| @classmethod | |||
| def update(cls, **kwargs): | |||
| cur = get_db().cursor() | |||
| query = 'UPDATE %s SET %s WHERE %s = ?' % ( | |||
| cls.__table_name__, | |||
| ', '.join("'%s' = ?" % str(x) for x in cls.__fields__),cls.__priamry_key__) | |||
| data = () | |||
| for f in cls.__fields__: | |||
| if f in cls.__json_fields__: | |||
| data = data + (json.dumps(kwargs.get(f)),) | |||
| else: | |||
| data = data + (kwargs.get(f),) | |||
| data = data + (kwargs.get(cls.__priamry_key__),) | |||
| cur.execute(query, data) | |||
| get_db().commit() | |||
| return cls(kwargs) | |||
| import sqlite3 | |||
| from flask import json, g | |||
| def get_db(): | |||
| db = getattr(g, '_database', None) | |||
| if db is None: | |||
| def dict_factory(cursor, row): | |||
| d = {} | |||
| for idx, col in enumerate(cursor.description): | |||
| d[col[0]] = row[idx] | |||
| return d | |||
| db = g._database = sqlite3.connect('craftbeerpi.db') | |||
| db.row_factory = dict_factory | |||
| return db | |||
| class DBModel(object): | |||
| __priamry_key__ = "id" | |||
| __as_array__ = False | |||
| __order_by__ = None | |||
| __json_fields__ = [] | |||
| def __init__(self, args): | |||
| self.__setattr__(self.__priamry_key__, args.get(self.__priamry_key__)) | |||
| for f in self.__fields__: | |||
| if f in self.__json_fields__: | |||
| if args.get(f) is not None: | |||
| if isinstance(args.get(f) , dict) or isinstance(args.get(f) , list) : | |||
| self.__setattr__(f, args.get(f)) | |||
| else: | |||
| self.__setattr__(f, json.loads(args.get(f))) | |||
| else: | |||
| self.__setattr__(f, None) | |||
| else: | |||
| self.__setattr__(f, args.get(f)) | |||
| @classmethod | |||
| def get_all(cls): | |||
| cur = get_db().cursor() | |||
| if cls.__order_by__ is not None: | |||
| 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 = {} | |||
| for r in cur.fetchall(): | |||
| result[r.get(cls.__priamry_key__)] = cls(r) | |||
| return result | |||
| @classmethod | |||
| def get_one(cls, id): | |||
| cur = get_db().cursor() | |||
| cur.execute("SELECT * FROM %s WHERE %s = ?" % (cls.__table_name__, cls.__priamry_key__), (id,)) | |||
| r = cur.fetchone() | |||
| if r is not None: | |||
| return cls(r) | |||
| else: | |||
| return None | |||
| @classmethod | |||
| def delete(cls, id): | |||
| cur = get_db().cursor() | |||
| cur.execute("DELETE FROM %s WHERE %s = ? " % (cls.__table_name__, cls.__priamry_key__), (id,)) | |||
| get_db().commit() | |||
| @classmethod | |||
| def insert(cls, **kwargs): | |||
| cur = get_db().cursor() | |||
| if cls.__priamry_key__ is not None and kwargs.has_key(cls.__priamry_key__): | |||
| query = "INSERT INTO %s (%s, %s) VALUES (?, %s)" % ( | |||
| cls.__table_name__, | |||
| cls.__priamry_key__, | |||
| ', '.join("'%s'" % str(x) for x in cls.__fields__), | |||
| ', '.join(['?'] * len(cls.__fields__))) | |||
| data = () | |||
| data = data + (kwargs.get(cls.__priamry_key__),) | |||
| for f in cls.__fields__: | |||
| if f in cls.__json_fields__: | |||
| data = data + (json.dumps(kwargs.get(f)),) | |||
| else: | |||
| data = data + (kwargs.get(f),) | |||
| else: | |||
| query = 'INSERT INTO %s (%s) VALUES (%s)' % ( | |||
| cls.__table_name__, | |||
| ', '.join("'%s'" % str(x) for x in cls.__fields__), | |||
| ', '.join(['?'] * len(cls.__fields__))) | |||
| data = () | |||
| for f in cls.__fields__: | |||
| if f in cls.__json_fields__: | |||
| data = data + (json.dumps(kwargs.get(f)),) | |||
| else: | |||
| data = data + (kwargs.get(f),) | |||
| cur.execute(query, data) | |||
| get_db().commit() | |||
| i = cur.lastrowid | |||
| kwargs["id"] = i | |||
| return cls(kwargs) | |||
| @classmethod | |||
| def update(cls, **kwargs): | |||
| cur = get_db().cursor() | |||
| query = 'UPDATE %s SET %s WHERE %s = ?' % ( | |||
| cls.__table_name__, | |||
| ', '.join("'%s' = ?" % str(x) for x in cls.__fields__),cls.__priamry_key__) | |||
| data = () | |||
| for f in cls.__fields__: | |||
| if f in cls.__json_fields__: | |||
| data = data + (json.dumps(kwargs.get(f)),) | |||
| else: | |||
| data = data + (kwargs.get(f),) | |||
| data = data + (kwargs.get(cls.__priamry_key__),) | |||
| cur.execute(query, data) | |||
| get_db().commit() | |||
| return cls(kwargs) | |||
| @@ -1,47 +0,0 @@ | |||
| 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) | |||
| @@ -1,108 +0,0 @@ | |||
| # -*- coding: utf-8 -*- | |||
| class Base(object): | |||
| __dirty = False | |||
| @classmethod | |||
| def init_global(cls): | |||
| 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): | |||
| pass | |||
| def stop(self): | |||
| pass | |||
| def update(self, **kwds): | |||
| pass | |||
| def __init__(self, *args, **kwds): | |||
| for a in kwds: | |||
| super(Base, self).__setattr__(a, kwds.get(a)) | |||
| self.api = kwds.get("api") | |||
| self.id = kwds.get("id") | |||
| self.value = None | |||
| self.__dirty = False | |||
| def __setattr__(self, name, value): | |||
| if name != "_Base__dirty": | |||
| self.__dirty = True | |||
| super(Base, self).__setattr__(name, value) | |||
| else: | |||
| super(Base, self).__setattr__(name, value) | |||
| class SensorBase(Base): | |||
| last_value = 0 | |||
| def init(self): | |||
| print "INIT Base SENSOR" | |||
| def stop(self): | |||
| print "STOP SENSOR" | |||
| def data_received(self, data): | |||
| self.last_value = data | |||
| self.api.receive_sensor_value(self.id, data) | |||
| def get_unit(self): | |||
| if self.get_config_parameter("unit", "C") == "C": | |||
| return "°C" | |||
| else: | |||
| return "°F" | |||
| def get_value(self): | |||
| return {"value": self.last_value, "unit": self.get_unit()} | |||
| class SensorActive(SensorBase): | |||
| __running = False | |||
| def is_running(self): | |||
| return self.__running | |||
| def init(self): | |||
| self.__running = True | |||
| def stop(self): | |||
| self.__running = False | |||
| def execute(self): | |||
| pass | |||
| class SensorPassive(SensorBase): | |||
| def init(self): | |||
| print "INIT PASSIV SENSOR" | |||
| pass | |||
| def read(self): | |||
| return 0 | |||
| class ActorBase(Base): | |||
| def state(self): | |||
| return 1 | |||
| def set_power(self, power): | |||
| pass | |||
| def on(self, power=0): | |||
| pass | |||
| def off(self): | |||
| pass | |||
| @@ -0,0 +1,36 @@ | |||
| import flask_login | |||
| from modules.core.core import cbpi, addon | |||
| class User(flask_login.UserMixin): | |||
| pass | |||
| @addon.core.initializer(order=0) | |||
| def log(cbpi): | |||
| cbpi._login_manager = flask_login.LoginManager() | |||
| cbpi._login_manager.init_app(cbpi._app) | |||
| @cbpi._app.route('/login', methods=['GET', 'POST']) | |||
| def login(): | |||
| user = User() | |||
| user.id = "manuel" | |||
| flask_login.login_user(user) | |||
| return "OK" | |||
| @cbpi._app.route('/logout') | |||
| def logout(): | |||
| flask_login.logout_user() | |||
| return 'Logged out' | |||
| @cbpi._login_manager.user_loader | |||
| def user_loader(email): | |||
| if email not in cbpi.cache["users"]: | |||
| return | |||
| user = User() | |||
| user.id = email | |||
| return user | |||
| @cbpi._login_manager.unauthorized_handler | |||
| def unauthorized_handler(): | |||
| return 'Unauthorized :-(' | |||
| @@ -1,68 +1,48 @@ | |||
| class PropertyType(object): | |||
| pass | |||
| class Property(object): | |||
| class Select(PropertyType): | |||
| 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=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="", description=""): | |||
| PropertyType.__init__(self) | |||
| self.label = label | |||
| self.configurable = configurable | |||
| self.default_value = default_value | |||
| self.description = description | |||
| class Actor(PropertyType): | |||
| def __init__(self, label, description=""): | |||
| PropertyType.__init__(self) | |||
| self.label = label | |||
| self.configurable = True | |||
| self.description = description | |||
| class Sensor(PropertyType): | |||
| def __init__(self, label, description=""): | |||
| PropertyType.__init__(self) | |||
| self.label = label | |||
| self.configurable = True | |||
| self.description = description | |||
| class Kettle(PropertyType): | |||
| def __init__(self, label, description=""): | |||
| PropertyType.__init__(self) | |||
| self.label = label | |||
| self.configurable = True | |||
| self.description = description | |||
| class StepProperty(Property): | |||
| class Actor(PropertyType): | |||
| def __init__(self, label, 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 PropertyType(object): | |||
| pass | |||
| class Property(object): | |||
| class Select(PropertyType): | |||
| 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=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, required=False, default_value="", description=""): | |||
| PropertyType.__init__(self) | |||
| self.label = label | |||
| self.required = required | |||
| self.configurable = configurable | |||
| self.default_value = default_value | |||
| self.description = description | |||
| class Actor(PropertyType): | |||
| def __init__(self, label, description=""): | |||
| PropertyType.__init__(self) | |||
| self.label = label | |||
| self.configurable = True | |||
| self.description = description | |||
| class Sensor(PropertyType): | |||
| def __init__(self, label, description=""): | |||
| PropertyType.__init__(self) | |||
| self.label = label | |||
| self.configurable = True | |||
| self.description = description | |||
| class Kettle(PropertyType): | |||
| def __init__(self, label, description=""): | |||
| PropertyType.__init__(self) | |||
| self.label = label | |||
| self.configurable = True | |||
| self.description = description | |||
| @@ -1,146 +0,0 @@ | |||
| from modules import cbpi | |||
| from modules.core.props import StepProperty, Property | |||
| import time | |||
| class NotificationAPI(object): | |||
| def notify(self, headline, message, type="success", timeout=5000): | |||
| self.api.notify(headline, message, type, timeout) | |||
| class ActorAPI(NotificationAPI): | |||
| @cbpi.try_catch(None) | |||
| def actor_on(self, id, power=100): | |||
| self.api.switch_actor_on(int(id), power=power) | |||
| @cbpi.try_catch(None) | |||
| def actor_off(self, id): | |||
| self.api.switch_actor_off(int(id)) | |||
| @cbpi.try_catch(None) | |||
| def actor_power(self, id, power): | |||
| self.api.actor_power(int(id), power) | |||
| class SensorAPI(NotificationAPI): | |||
| @cbpi.try_catch(None) | |||
| def get_sensor_value(self, id): | |||
| return cbpi.get_sensor_value(id) | |||
| class KettleAPI(NotificationAPI): | |||
| @cbpi.try_catch(None) | |||
| def get_kettle_temp(self, id=None): | |||
| id = int(id) | |||
| if id is None: | |||
| id = self.kettle_id | |||
| return cbpi.get_sensor_value(int(self.api.cache.get("kettle").get(id).sensor)) | |||
| @cbpi.try_catch(None) | |||
| def get_target_temp(self, id=None): | |||
| id = int(id) | |||
| if id is None: | |||
| id = self.kettle_id | |||
| return self.api.cache.get("kettle").get(id).target_temp | |||
| def set_target_temp(self, temp, id=None): | |||
| temp = float(temp) | |||
| try: | |||
| if id is None: | |||
| self.api.emit_event("SET_TARGET_TEMP", id=self.kettle_id, temp=temp) | |||
| else: | |||
| self.api.emit_event("SET_TARGET_TEMP", id=id, temp=temp) | |||
| except Exception as e: | |||
| self.notify("Faild to set Target Temp", "", type="warning") | |||
| class Timer(object): | |||
| timer_end = Property.Number("TIMER_END", configurable=False) | |||
| def start_timer(self, timer): | |||
| if self.timer_end is not None: | |||
| return | |||
| self.timer_end = int(time.time()) + timer | |||
| def stop_timer(self): | |||
| if self.timer_end is not None: | |||
| self.timer_end = None | |||
| def is_timer_running(self): | |||
| if self.timer_end is not None: | |||
| return True | |||
| else: | |||
| return False | |||
| def timer_remaining(self): | |||
| if self.timer_end is not None: | |||
| return self.timer_end - int(time.time()) | |||
| else: | |||
| return None | |||
| def is_timer_finished(self): | |||
| if self.timer_end is None: | |||
| return None | |||
| if self.timer_end <= int(time.time()): | |||
| return True | |||
| else: | |||
| return False | |||
| class StepBase(Timer, ActorAPI, SensorAPI, KettleAPI): | |||
| __dirty = False | |||
| managed_fields = [] | |||
| n = False | |||
| def next(self): | |||
| self.n = True | |||
| def init(self): | |||
| pass | |||
| def finish(self): | |||
| pass | |||
| def reset(self): | |||
| pass | |||
| def execute(self): | |||
| print "-------------" | |||
| print "Step Info" | |||
| print "Kettle ID: %s" % self.kettle_id | |||
| print "ID: %s" % self.id | |||
| def __init__(self, *args, **kwds): | |||
| for a in kwds: | |||
| super(StepBase, self).__setattr__(a, kwds.get(a)) | |||
| self.api = kwds.get("api") | |||
| self.id = kwds.get("id") | |||
| self.name = kwds.get("name") | |||
| self.kettle_id = kwds.get("kettleid") | |||
| self.value = None | |||
| self.__dirty = False | |||
| def is_dirty(self): | |||
| return self.__dirty | |||
| def reset_dirty(self): | |||
| self.__dirty = False | |||
| def __setattr__(self, name, value): | |||
| if name != "_StepBase__dirty" and name in self.managed_fields: | |||
| self.__dirty = True | |||
| super(StepBase, self).__setattr__(name, value) | |||
| else: | |||
| super(StepBase, self).__setattr__(name, value) | |||
| @@ -0,0 +1,134 @@ | |||
| from modules.core.db import DBModel, get_db | |||
| class Kettle(DBModel): | |||
| __fields__ = ["name","sensor", "heater", "automatic", "logic", "config", "agitator", "target_temp"] | |||
| __table_name__ = "kettle" | |||
| __json_fields__ = ["config"] | |||
| class Sensor(DBModel): | |||
| __fields__ = ["name","type", "config", "hide"] | |||
| __table_name__ = "sensor" | |||
| __json_fields__ = ["config"] | |||
| class Config(DBModel): | |||
| __fields__ = ["type", "value", "description", "options"] | |||
| __table_name__ = "config" | |||
| __json_fields__ = ["options"] | |||
| __priamry_key__ = "name" | |||
| class Actor(DBModel): | |||
| __fields__ = ["name","type", "config", "hide"] | |||
| __table_name__ = "actor" | |||
| __json_fields__ = ["config"] | |||
| class Step(DBModel): | |||
| __fields__ = ["name","type", "stepstate", "state", "start", "end", "order", "config"] | |||
| __table_name__ = "step" | |||
| __json_fields__ = ["config", "stepstate"] | |||
| __order_by__ = "order" | |||
| __as_array__ = True | |||
| @classmethod | |||
| def get_max_order(cls): | |||
| cur = get_db().cursor() | |||
| cur.execute("SELECT max(step.'order') as 'order' FROM %s" % cls.__table_name__) | |||
| r = cur.fetchone() | |||
| return r.get("order") | |||
| @classmethod | |||
| def get_by_state(cls, state, order=True): | |||
| cur = get_db().cursor() | |||
| 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) | |||
| else: | |||
| return None | |||
| @classmethod | |||
| def delete_all(cls): | |||
| cur = get_db().cursor() | |||
| cur.execute("DELETE FROM %s" % cls.__table_name__) | |||
| get_db().commit() | |||
| @classmethod | |||
| def reset_all_steps(cls): | |||
| cur = get_db().cursor() | |||
| cur.execute("UPDATE %s SET state = 'I', stepstate = NULL , start = NULL, end = NULL " % cls.__table_name__) | |||
| get_db().commit() | |||
| @classmethod | |||
| def update_state(cls, id, state): | |||
| cur = get_db().cursor() | |||
| cur.execute("UPDATE %s SET state = ? WHERE id =?" % cls.__table_name__, (state, id)) | |||
| get_db().commit() | |||
| @classmethod | |||
| def update_step_state(cls, id, state): | |||
| cur = get_db().cursor() | |||
| 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 Fermenter(DBModel): | |||
| __fields__ = ["name", "brewname", "sensor", "sensor2", "sensor3", "heater", "cooler", "logic", "config", "target_temp"] | |||
| __table_name__ = "fermenter" | |||
| __json_fields__ = ["config"] | |||
| class FermenterStep(DBModel): | |||
| __fields__ = ["name", "days", "hours", "minutes", "temp", "direction", "order", "state", "start", "end", "timer_start", "fermenter_id"] | |||
| __table_name__ = "fermenter_step" | |||
| @classmethod | |||
| def get_by_fermenter_id(cls, id): | |||
| cur = get_db().cursor() | |||
| cur.execute("SELECT * FROM %s WHERE fermenter_id = ?" % cls.__table_name__,(id,)) | |||
| result = [] | |||
| for r in cur.fetchall(): | |||
| result.append(cls(r)) | |||
| return result | |||
| @classmethod | |||
| def get_max_order(cls,id): | |||
| cur = get_db().cursor() | |||
| cur.execute("SELECT max(fermenter_step.'order') as 'order' FROM %s WHERE fermenter_id = ?" % cls.__table_name__, (id,)) | |||
| r = cur.fetchone() | |||
| return r.get("order") | |||
| @classmethod | |||
| def update_state(cls, id, state): | |||
| cur = get_db().cursor() | |||
| cur.execute("UPDATE %s SET state = ? WHERE id =?" % cls.__table_name__, (state, id)) | |||
| get_db().commit() | |||
| @classmethod | |||
| def update_timer(cls, id, timer): | |||
| cur = get_db().cursor() | |||
| cur.execute("UPDATE %s SET timer_start = ? WHERE id =?" % cls.__table_name__, (timer, id)) | |||
| get_db().commit() | |||
| @classmethod | |||
| def get_by_state(cls, state): | |||
| cur = get_db().cursor() | |||
| cur.execute("SELECT * FROM %s WHERE state = ?" % cls.__table_name__, state) | |||
| r = cur.fetchone() | |||
| if r is not None: | |||
| return cls(r) | |||
| else: | |||
| return None | |||
| @classmethod | |||
| def reset_all_steps(cls,id): | |||
| cur = get_db().cursor() | |||
| cur.execute("UPDATE %s SET state = 'I', start = NULL, end = NULL, timer_start = NULL WHERE fermenter_id = ?" % cls.__table_name__, (id,)) | |||
| get_db().commit() | |||
| @@ -2,63 +2,12 @@ import time | |||
| from flask import request | |||
| from flask_classy import route | |||
| from modules import DBModel, cbpi, get_db | |||
| from modules.core.core import cbpi | |||
| from modules.core.db import get_db, DBModel | |||
| from modules.core.baseview import BaseView | |||
| from modules.database.dbmodel import Fermenter, FermenterStep | |||
| class Fermenter(DBModel): | |||
| __fields__ = ["name", "brewname", "sensor", "sensor2", "sensor3", "heater", "cooler", "logic", "config", "target_temp"] | |||
| __table_name__ = "fermenter" | |||
| __json_fields__ = ["config"] | |||
| class FermenterStep(DBModel): | |||
| __fields__ = ["name", "days", "hours", "minutes", "temp", "direction", "order", "state", "start", "end", "timer_start", "fermenter_id"] | |||
| __table_name__ = "fermenter_step" | |||
| @classmethod | |||
| def get_by_fermenter_id(cls, id): | |||
| cur = get_db().cursor() | |||
| cur.execute("SELECT * FROM %s WHERE fermenter_id = ?" % cls.__table_name__,(id,)) | |||
| result = [] | |||
| for r in cur.fetchall(): | |||
| result.append(cls(r)) | |||
| return result | |||
| @classmethod | |||
| def get_max_order(cls,id): | |||
| cur = get_db().cursor() | |||
| cur.execute("SELECT max(fermenter_step.'order') as 'order' FROM %s WHERE fermenter_id = ?" % cls.__table_name__, (id,)) | |||
| r = cur.fetchone() | |||
| return r.get("order") | |||
| @classmethod | |||
| def update_state(cls, id, state): | |||
| cur = get_db().cursor() | |||
| cur.execute("UPDATE %s SET state = ? WHERE id =?" % cls.__table_name__, (state, id)) | |||
| get_db().commit() | |||
| @classmethod | |||
| def update_timer(cls, id, timer): | |||
| cur = get_db().cursor() | |||
| cur.execute("UPDATE %s SET timer_start = ? WHERE id =?" % cls.__table_name__, (timer, id)) | |||
| get_db().commit() | |||
| @classmethod | |||
| def get_by_state(cls, state): | |||
| cur = get_db().cursor() | |||
| cur.execute("SELECT * FROM %s WHERE state = ?" % cls.__table_name__, state) | |||
| r = cur.fetchone() | |||
| if r is not None: | |||
| return cls(r) | |||
| else: | |||
| return None | |||
| @classmethod | |||
| def reset_all_steps(cls,id): | |||
| cur = get_db().cursor() | |||
| cur.execute("UPDATE %s SET state = 'I', start = NULL, end = NULL, timer_start = NULL WHERE fermenter_id = ?" % cls.__table_name__, (id,)) | |||
| get_db().commit() | |||
| class FermenterView(BaseView): | |||
| model = Fermenter | |||
| cache_key = "fermenter" | |||
| @@ -86,7 +35,7 @@ class FermenterView(BaseView): | |||
| temp = float(temp) | |||
| cbpi.cache.get(self.cache_key)[id].target_temp = float(temp) | |||
| self.model.update(**self.api.cache.get(self.cache_key)[id].__dict__) | |||
| cbpi.emit("UPDATE_FERMENTER_TARGET_TEMP", {"id": id, "target_temp": temp}) | |||
| cbpi.ws_emit("UPDATE_FERMENTER_TARGET_TEMP", {"id": id, "target_temp": temp}) | |||
| return ('', 204) | |||
| @route('/<int:id>/brewname', methods=['POST']) | |||
| @@ -95,7 +44,7 @@ class FermenterView(BaseView): | |||
| brewname = data.get("brewname") | |||
| cbpi.cache.get(self.cache_key)[id].brewname = brewname | |||
| self.model.update(**self.api.cache.get(self.cache_key)[id].__dict__) | |||
| cbpi.emit("UPDATE_FERMENTER_BREWNAME", {"id": id, "brewname": brewname}) | |||
| cbpi.ws_emit("UPDATE_FERMENTER_BREWNAME", {"id": id, "brewname": brewname}) | |||
| return ('', 204) | |||
| @classmethod | |||
| @@ -119,7 +68,7 @@ class FermenterView(BaseView): | |||
| cbpi.cache.get(self.cache_key)[id].steps.append(f) | |||
| cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id]) | |||
| cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id]) | |||
| return ('', 204) | |||
| @@ -141,7 +90,7 @@ class FermenterView(BaseView): | |||
| FermenterStep.update(**s.__dict__) | |||
| break | |||
| cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id]) | |||
| cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id]) | |||
| return ('', 204) | |||
| @route('/<int:id>/step/<int:stepid>', methods=["DELETE"]) | |||
| @@ -152,7 +101,7 @@ class FermenterView(BaseView): | |||
| del cbpi.cache.get(self.cache_key)[id].steps[idx] | |||
| FermenterStep.delete(s.id) | |||
| break | |||
| cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id]) | |||
| cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id]) | |||
| return ('', 204) | |||
| @route('/<int:id>/start', methods=['POST']) | |||
| @@ -179,7 +128,8 @@ class FermenterView(BaseView): | |||
| if inactive is not None: | |||
| fermenter = self.get_fermenter(inactive.fermenter_id) | |||
| current_temp = cbpi.get_sensor_value(int(fermenter.sensor)) | |||
| current_temp = cbpi.sensor.get_value(int(fermenter.sensor)) | |||
| inactive.state = 'A' | |||
| inactive.start = time.time() | |||
| @@ -190,7 +140,7 @@ class FermenterView(BaseView): | |||
| cbpi.cache["fermenter_task"][id] = inactive | |||
| cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id]) | |||
| cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id]) | |||
| return ('', 204) | |||
| @route('/<int:id>/reset', methods=["POST"]) | |||
| @@ -202,7 +152,7 @@ class FermenterView(BaseView): | |||
| if id in cbpi.cache["fermenter_task"]: | |||
| del cbpi.cache["fermenter_task"][id] | |||
| cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id]) | |||
| cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id]) | |||
| return ('', 204) | |||
| @route('/<int:id>/automatic', methods=['POST']) | |||
| @@ -216,21 +166,23 @@ class FermenterView(BaseView): | |||
| cfg = fermenter.config.copy() | |||
| cfg.update( | |||
| dict(api=cbpi, fermenter_id=fermenter.id, heater=fermenter.heater, sensor=fermenter.sensor)) | |||
| instance = cbpi.get_fermentation_controller(fermenter.logic).get("class")(**cfg) | |||
| instance = cbpi.fermentation.get_controller(fermenter.logic).get("class")(**cfg) | |||
| instance.init() | |||
| fermenter.instance = instance | |||
| def run(instance): | |||
| instance.run() | |||
| t = cbpi.socketio.start_background_task(target=run, instance=instance) | |||
| t = cbpi._socketio.start_background_task(target=run, instance=instance) | |||
| fermenter.state = not fermenter.state | |||
| cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key).get(id)) | |||
| cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key).get(id)) | |||
| cbpi.emit("FERMENTER_CONTROLLER_STARTED", id=id) | |||
| else: | |||
| # Stop controller | |||
| fermenter.instance.stop() | |||
| fermenter.state = not fermenter.state | |||
| cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key).get(id)) | |||
| cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key).get(id)) | |||
| cbpi.emit("FERMENTER_CONTROLLER_STOPPED", id=id) | |||
| except Exception as e: | |||
| print e | |||
| @@ -255,7 +207,7 @@ class FermenterView(BaseView): | |||
| step.timer_start = target_time | |||
| cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id]) | |||
| cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id]) | |||
| def check_step(self): | |||
| for key, value in cbpi.cache["fermenter_task"].iteritems(): | |||
| @@ -282,35 +234,33 @@ class FermenterView(BaseView): | |||
| pass | |||
| @cbpi.backgroundtask(key="read_target_temps_fermenter", interval=5) | |||
| def read_target_temps(api): | |||
| @cbpi.addon.core.backgroundjob(key="read_target_temps_fermenter", interval=5) | |||
| def read_target_temps(cbpi): | |||
| """ | |||
| background process that reads all passive sensors in interval of 1 second | |||
| :return: None | |||
| """ | |||
| result = {} | |||
| for key, value in cbpi.cache.get("fermenter").iteritems(): | |||
| cbpi.save_to_file(key, value.target_temp, prefix="fermenter") | |||
| cbpi.sensor.write_log(key, value.target_temp, prefix="fermenter") | |||
| instance = FermenterView() | |||
| @cbpi.backgroundtask(key="fermentation_task", interval=1) | |||
| def execute_fermentation_step(api): | |||
| with cbpi.app.app_context(): | |||
| @cbpi.addon.core.backgroundjob(key="fermentation_task", interval=1) | |||
| def execute_fermentation_step(cbpi): | |||
| with cbpi._app.app_context(): | |||
| instance.check_step() | |||
| def init_active_steps(): | |||
| ''' | |||
| active_steps = FermenterStep.query.filter_by(state='A') | |||
| for a in active_steps: | |||
| db.session.expunge(a) | |||
| cbpi.cache["fermenter_task"][a.fermenter_id] = a | |||
| ''' | |||
| @cbpi.initalizer(order=1) | |||
| pass | |||
| @cbpi.addon.core.initializer(order=1) | |||
| def init(cbpi): | |||
| FermenterView.register(cbpi.app, route_base='/api/fermenter') | |||
| cbpi.cache["fermenter_task"] = {} | |||
| FermenterView.register(cbpi._app, route_base='/api/fermenter') | |||
| FermenterView.init_cache() | |||
| @@ -1,96 +1,264 @@ | |||
| from flask import request | |||
| from flask_classy import FlaskView, route | |||
| from modules import cbpi, socketio | |||
| from modules.core.baseview import BaseView | |||
| from modules.core.db import DBModel | |||
| class Kettle(DBModel): | |||
| __fields__ = ["name","sensor", "heater", "automatic", "logic", "config", "agitator", "target_temp"] | |||
| __table_name__ = "kettle" | |||
| __json_fields__ = ["config"] | |||
| class Kettle2View(BaseView): | |||
| model = Kettle | |||
| cache_key = "kettle" | |||
| @classmethod | |||
| def _pre_post_callback(self, data): | |||
| data["target_temp"] = 0 | |||
| @classmethod | |||
| def post_init_callback(cls, obj): | |||
| obj.state = False | |||
| def _post_post_callback(self, m): | |||
| m.state = False | |||
| def _pre_put_callback(self, m): | |||
| try: | |||
| m.instance.stop() | |||
| except: | |||
| pass | |||
| def _post_put_callback(self, m): | |||
| m.state = False | |||
| @route('/<int:id>/targettemp/<temp>', methods=['POST']) | |||
| def postTargetTemp(self, id, temp): | |||
| id = int(id) | |||
| temp = float(temp) | |||
| cbpi.cache.get("kettle")[id].target_temp = float(temp) | |||
| self.model.update(**self.api.cache.get(self.cache_key)[id].__dict__) | |||
| cbpi.emit("UPDATE_KETTLE_TARGET_TEMP", {"id": id, "target_temp": temp}) | |||
| return ('', 204) | |||
| @route('/<int:id>/automatic', methods=['POST']) | |||
| def toggle(self, id): | |||
| kettle = cbpi.cache.get("kettle")[id] | |||
| if kettle.state is False: | |||
| # Start controller | |||
| if kettle.logic is not None: | |||
| cfg = kettle.config.copy() | |||
| cfg.update(dict(api=cbpi, kettle_id=kettle.id, heater=kettle.heater, sensor=kettle.sensor)) | |||
| instance = cbpi.get_controller(kettle.logic).get("class")(**cfg) | |||
| instance.init() | |||
| kettle.instance = instance | |||
| def run(instance): | |||
| instance.run() | |||
| t = self.api.socketio.start_background_task(target=run, instance=instance) | |||
| kettle.state = not kettle.state | |||
| cbpi.emit("UPDATE_KETTLE", cbpi.cache.get("kettle").get(id)) | |||
| else: | |||
| # Stop controller | |||
| kettle.instance.stop() | |||
| kettle.state = not kettle.state | |||
| cbpi.emit("UPDATE_KETTLE", cbpi.cache.get("kettle").get(id)) | |||
| return ('', 204) | |||
| @cbpi.event("SET_TARGET_TEMP") | |||
| def set_target_temp(id, temp): | |||
| ''' | |||
| Change Taget Temp Event | |||
| :param id: kettle id | |||
| :param temp: target temp to set | |||
| :return: None | |||
| ''' | |||
| Kettle2View().postTargetTemp(id,temp) | |||
| @cbpi.backgroundtask(key="read_target_temps", interval=5) | |||
| def read_target_temps(api): | |||
| """ | |||
| background process that reads all passive sensors in interval of 1 second | |||
| :return: None | |||
| """ | |||
| result = {} | |||
| for key, value in cbpi.cache.get("kettle").iteritems(): | |||
| cbpi.save_to_file(key, value.target_temp, prefix="kettle") | |||
| @cbpi.initalizer() | |||
| def init(cbpi): | |||
| Kettle2View.api = cbpi | |||
| Kettle2View.register(cbpi.app,route_base='/api/kettle') | |||
| Kettle2View.init_cache() | |||
| from flask import request | |||
| from flask_classy import FlaskView, route | |||
| from modules.core.core import cbpi | |||
| from modules.core.baseview import BaseView | |||
| from modules.core.db import DBModel | |||
| from modules.database.dbmodel import Kettle | |||
| class KettleView(BaseView): | |||
| model = Kettle | |||
| cache_key = "kettle" | |||
| @route('/', methods=["GET"]) | |||
| def getAll(self): | |||
| """ | |||
| Get all Kettles | |||
| --- | |||
| tags: | |||
| - kettle | |||
| responses: | |||
| 200: | |||
| description: List auf all Kettles | |||
| """ | |||
| return super(KettleView, self).getAll() | |||
| @route('/', methods=["POST"]) | |||
| def post(self): | |||
| """ | |||
| Create a new kettle | |||
| --- | |||
| tags: | |||
| - kettle | |||
| parameters: | |||
| - in: body | |||
| name: body | |||
| schema: | |||
| id: Kettle | |||
| required: | |||
| - name | |||
| properties: | |||
| name: | |||
| type: string | |||
| description: name for user | |||
| sensor: | |||
| type: string | |||
| description: name for user | |||
| heater: | |||
| type: string | |||
| description: name for user | |||
| automatic: | |||
| type: string | |||
| description: name for user | |||
| logic: | |||
| type: string | |||
| description: name for user | |||
| config: | |||
| type: string | |||
| description: name for user | |||
| agitator: | |||
| type: string | |||
| description: name for user | |||
| target_temp: | |||
| type: string | |||
| description: name for user | |||
| responses: | |||
| 200: | |||
| description: User created | |||
| """ | |||
| return super(KettleView, self).post() | |||
| @route('/<int:id>', methods=["PUT"]) | |||
| def put(self, id): | |||
| """ | |||
| Update a kettle | |||
| --- | |||
| tags: | |||
| - kettle | |||
| parameters: | |||
| - in: path | |||
| name: id | |||
| schema: | |||
| type: integer | |||
| required: true | |||
| description: Numeric ID of the Kettle | |||
| - in: body | |||
| name: body | |||
| schema: | |||
| id: Kettle | |||
| required: | |||
| - name | |||
| properties: | |||
| name: | |||
| type: string | |||
| description: name for user | |||
| sensor: | |||
| type: string | |||
| description: name for user | |||
| heater: | |||
| type: string | |||
| description: name for user | |||
| automatic: | |||
| type: string | |||
| description: name for user | |||
| logic: | |||
| type: string | |||
| description: name for user | |||
| config: | |||
| type: string | |||
| description: name for user | |||
| agitator: | |||
| type: string | |||
| description: name for user | |||
| target_temp: | |||
| type: string | |||
| description: name for user | |||
| responses: | |||
| 200: | |||
| description: User created | |||
| """ | |||
| return super(KettleView, self).put(id) | |||
| @route('/<int:id>', methods=["DELETE"]) | |||
| def delete(self, id): | |||
| """ | |||
| Delete a kettle | |||
| --- | |||
| tags: | |||
| - kettle | |||
| parameters: | |||
| - in: path | |||
| name: id | |||
| schema: | |||
| type: integer | |||
| required: true | |||
| description: Numeric ID of the Kettle | |||
| responses: | |||
| 200: | |||
| description: User created | |||
| """ | |||
| return super(KettleView, self).delete(id) | |||
| @classmethod | |||
| def _pre_post_callback(self, data): | |||
| data["target_temp"] = 0 | |||
| @classmethod | |||
| def post_init_callback(cls, obj): | |||
| obj.state = False | |||
| def _post_post_callback(self, m): | |||
| m.state = False | |||
| def _pre_put_callback(self, m): | |||
| try: | |||
| m.instance.stop() | |||
| except: | |||
| pass | |||
| def _post_put_callback(self, m): | |||
| m.state = False | |||
| @route('/<int:id>/targettemp/<temp>', methods=['POST']) | |||
| def postTargetTemp(self, id, temp): | |||
| """ | |||
| Set Target Temp | |||
| --- | |||
| tags: | |||
| - kettle | |||
| parameters: | |||
| - required: true | |||
| type: string | |||
| description: ID of pet to return | |||
| in: path | |||
| name: id | |||
| - required: true | |||
| type: string | |||
| description: Temperature you like to set | |||
| in: path | |||
| name: temp | |||
| responses: | |||
| 201: | |||
| description: User created | |||
| """ | |||
| id = int(id) | |||
| temp = float(temp) | |||
| cbpi.brewing.set_target_temp(id, temp) | |||
| return ('', 204) | |||
| @route('/<int:id>/automatic', methods=['POST']) | |||
| def toggle(self, id): | |||
| """ | |||
| Set Target Temp | |||
| --- | |||
| tags: | |||
| - kettle | |||
| parameters: | |||
| - required: true | |||
| type: string | |||
| description: ID of pet to return | |||
| in: path | |||
| name: id | |||
| - required: true | |||
| type: string | |||
| description: Temperature you like to set | |||
| in: path | |||
| name: temp | |||
| responses: | |||
| 201: | |||
| description: User created | |||
| """ | |||
| kettle = cbpi.cache.get("kettle")[id] | |||
| if kettle.state is False: | |||
| # Start controller | |||
| if kettle.logic is not None: | |||
| cfg = kettle.config.copy() | |||
| cfg.update(dict(api=cbpi, kettle_id=kettle.id, heater=kettle.heater, sensor=kettle.sensor)) | |||
| instance = cbpi.brewing.get_controller(kettle.logic).get("class")(**cfg) | |||
| instance.init() | |||
| kettle.instance = instance | |||
| def run(instance): | |||
| instance.run() | |||
| t = self.api._socketio.start_background_task(target=run, instance=instance) | |||
| kettle.state = not kettle.state | |||
| cbpi.ws_emit("UPDATE_KETTLE", cbpi.cache.get("kettle").get(id)) | |||
| cbpi.emit("KETTLE_CONTROLLER_STARTED", id=id) | |||
| else: | |||
| # Stop controller | |||
| kettle.instance.stop() | |||
| kettle.state = not kettle.state | |||
| cbpi.ws_emit("UPDATE_KETTLE", cbpi.cache.get("kettle").get(id)) | |||
| cbpi.emit("KETTLE_CONTROLLER_STOPPED", id=id) | |||
| return ('', 204) | |||
| #@cbpi.event("SET_TARGET_TEMP") | |||
| def set_target_temp(id, temp): | |||
| ''' | |||
| Change Taget Temp Event | |||
| :param id: kettle id | |||
| :param temp: target temp to set | |||
| :return: None | |||
| ''' | |||
| KettleView().postTargetTemp(id, temp) | |||
| @cbpi.addon.core.backgroundjob(key="read_target_temps", interval=5) | |||
| def read_target_temps(api): | |||
| """ | |||
| background process that reads all passive sensors in interval of 1 second | |||
| :return: None | |||
| """ | |||
| result = {} | |||
| for key, value in cbpi.cache.get("kettle").iteritems(): | |||
| cbpi.sensor.write_log(key, value.target_temp, prefix="kettle") | |||
| @cbpi.addon.core.initializer() | |||
| def init(cbpi): | |||
| KettleView.api = cbpi | |||
| KettleView.register(cbpi._app, route_base='/api/kettle') | |||
| KettleView.init_cache() | |||
| @@ -1 +1,175 @@ | |||
| import endpoints | |||
| import datetime | |||
| import os | |||
| from flask import Blueprint, request, send_from_directory, json | |||
| from flask_classy import FlaskView, route | |||
| from modules.core.core import cbpi | |||
| class LogView(FlaskView): | |||
| @route('/', methods=['GET']) | |||
| def get_all_logfiles(self): | |||
| """ | |||
| Get a list of all Log Files | |||
| --- | |||
| tags: | |||
| - logs | |||
| responses: | |||
| 200: | |||
| description: List of all log files | |||
| """ | |||
| result = [] | |||
| for filename in os.listdir("./logs"): | |||
| if filename.endswith(".log"): | |||
| result.append(filename) | |||
| return json.dumps(result) | |||
| @route('/actions') | |||
| def actions(self): | |||
| """ | |||
| Get a list of all brewing actions | |||
| --- | |||
| tags: | |||
| - logs | |||
| responses: | |||
| 200: | |||
| description: List of all log files | |||
| """ | |||
| filename = "./logs/action.log" | |||
| if os.path.isfile(filename) == False: | |||
| return | |||
| import csv | |||
| array = [] | |||
| with open(filename, 'rb') as f: | |||
| reader = csv.reader(f) | |||
| for row in reader: | |||
| try: | |||
| array.append([int((datetime.datetime.strptime(row[0], "%Y-%m-%d %H:%M:%S") - datetime.datetime(1970, 1, 1)).total_seconds()) * 1000, row[1]]) | |||
| except: | |||
| pass | |||
| return json.dumps(array) | |||
| @route('/<file>', methods=["DELETE"]) | |||
| def clearlog(self, file): | |||
| """ | |||
| Delete a log file by name | |||
| --- | |||
| tags: | |||
| - logs | |||
| parameters: | |||
| - in: path | |||
| name: file | |||
| schema: | |||
| type: string | |||
| required: true | |||
| description: File name | |||
| responses: | |||
| 204: | |||
| description: Log deleted | |||
| """ | |||
| if not self.check_filename(file): | |||
| return ('File Not Found', 404) | |||
| filename = "./logs/%s" % file | |||
| if os.path.isfile(filename) == True: | |||
| os.remove(filename) | |||
| cbpi.notify("log deleted succesfully", "") | |||
| return ('', 204) | |||
| else: | |||
| cbpi.notify("Failed to delete log", "", type="danger") | |||
| return ('', 404) | |||
| def read_log_as_json(self, type, id): | |||
| filename = "./logs/%s_%s.log" % (type, id) | |||
| if os.path.isfile(filename) == False: | |||
| return | |||
| import csv | |||
| array = [] | |||
| with open(filename, 'rb') as f: | |||
| reader = csv.reader(f) | |||
| for row in reader: | |||
| try: | |||
| array.append([int((datetime.datetime.strptime(row[0], "%Y-%m-%d %H:%M:%S") - datetime.datetime(1970, 1, 1)).total_seconds()) * 1000, float(row[1])]) | |||
| except: | |||
| pass | |||
| return array | |||
| def convert_chart_data_to_json(self, chart_data): | |||
| return {"name": chart_data["name"], "data": self.read_log_as_json(chart_data["data_type"], chart_data["data_id"])} | |||
| @route('/<t>/<int:id>', methods=["POST"]) | |||
| def get_logs_as_json(self, t, id): | |||
| """ | |||
| Get Log as json | |||
| --- | |||
| tags: | |||
| - logs | |||
| parameters: | |||
| - in: path | |||
| name: id | |||
| schema: | |||
| type: string | |||
| required: true | |||
| description: id of the file | |||
| responses: | |||
| 200: | |||
| description: Log File Data | |||
| """ | |||
| data = request.json | |||
| result = [] | |||
| if t == "s": | |||
| name = cbpi.cache.get("sensors").get(id).name | |||
| result.append({"name": name, "data": self.read_log_as_json("sensor", id)}) | |||
| if t == "k": | |||
| kettle = cbpi.cache.get("kettle").get(id) | |||
| result = map(self.convert_chart_data_to_json, cbpi.brewing.get_controller(kettle.logic).get("class").chart(kettle)) | |||
| if t == "f": | |||
| fermenter = cbpi.cache.get("fermenter").get(id) | |||
| result = map(self.convert_chart_data_to_json, cbpi.fermentation.get_controller(fermenter.logic).get("class").chart(fermenter)) | |||
| return json.dumps(result) | |||
| @route('/download/<file>') | |||
| @cbpi.nocache | |||
| def download(self, file): | |||
| """ | |||
| Download a log file by name | |||
| --- | |||
| tags: | |||
| - logs | |||
| parameters: | |||
| - in: path | |||
| name: file | |||
| schema: | |||
| type: string | |||
| required: true | |||
| description: filename | |||
| responses: | |||
| 200: | |||
| description: Log file downloaded | |||
| """ | |||
| if not self.check_filename(file): | |||
| return ('File Not Found', 404) | |||
| return send_from_directory('../../logs', file, as_attachment=True, attachment_filename=file) | |||
| def check_filename(self, name): | |||
| import re | |||
| pattern = re.compile('^([A-Za-z0-9-_])+.log$') | |||
| return True if pattern.match(name) else False | |||
| @cbpi.addon.core.initializer() | |||
| def init(cbpi): | |||
| """ | |||
| Initializer for the message module | |||
| :param app: the flask app | |||
| :return: None | |||
| """ | |||
| LogView.register(cbpi._app, route_base='/api/logs') | |||
| @@ -1,109 +0,0 @@ | |||
| import datetime | |||
| import os | |||
| from flask import Blueprint, request, send_from_directory, json | |||
| from flask_classy import FlaskView, route | |||
| from modules import cbpi | |||
| class LogView(FlaskView): | |||
| @route('/', methods=['GET']) | |||
| def get_all_logfiles(self): | |||
| result = [] | |||
| for filename in os.listdir("./logs"): | |||
| if filename.endswith(".log"): | |||
| result.append(filename) | |||
| return json.dumps(result) | |||
| @route('/actions') | |||
| def actions(self): | |||
| filename = "./logs/action.log" | |||
| if os.path.isfile(filename) == False: | |||
| return | |||
| import csv | |||
| array = [] | |||
| with open(filename, 'rb') as f: | |||
| reader = csv.reader(f) | |||
| for row in reader: | |||
| try: | |||
| array.append([int((datetime.datetime.strptime(row[0], "%Y-%m-%d %H:%M:%S") - datetime.datetime(1970, 1, 1)).total_seconds()) * 1000, row[1]]) | |||
| except: | |||
| pass | |||
| return json.dumps(array) | |||
| @route('/<file>', methods=["DELETE"]) | |||
| def clearlog(self, file): | |||
| """ | |||
| Overload delete method to shutdown sensor before delete | |||
| :param id: sensor id | |||
| :return: HTTP 204 | |||
| """ | |||
| if not self.check_filename(file): | |||
| return ('File Not Found', 404) | |||
| filename = "./logs/%s" % file | |||
| if os.path.isfile(filename) == True: | |||
| os.remove(filename) | |||
| cbpi.notify("log deleted succesfully", "") | |||
| else: | |||
| cbpi.notify("Failed to delete log", "", type="danger") | |||
| return ('', 204) | |||
| def read_log_as_json(self, type, id): | |||
| filename = "./logs/%s_%s.log" % (type, id) | |||
| if os.path.isfile(filename) == False: | |||
| return | |||
| import csv | |||
| array = [] | |||
| with open(filename, 'rb') as f: | |||
| reader = csv.reader(f) | |||
| for row in reader: | |||
| try: | |||
| array.append([int((datetime.datetime.strptime(row[0], "%Y-%m-%d %H:%M:%S") - datetime.datetime(1970, 1, 1)).total_seconds()) * 1000, float(row[1])]) | |||
| except: | |||
| pass | |||
| return array | |||
| def convert_chart_data_to_json(self, chart_data): | |||
| return {"name": chart_data["name"], "data": self.read_log_as_json(chart_data["data_type"], chart_data["data_id"])} | |||
| @route('/<t>/<int:id>', methods=["POST"]) | |||
| def get_logs_as_json(self, t, id): | |||
| data = request.json | |||
| result = [] | |||
| if t == "s": | |||
| name = cbpi.cache.get("sensors").get(id).name | |||
| result.append({"name": name, "data": self.read_log_as_json("sensor", id)}) | |||
| if t == "k": | |||
| kettle = cbpi.cache.get("kettle").get(id) | |||
| result = map(self.convert_chart_data_to_json, cbpi.get_controller(kettle.logic).get("class").chart(kettle)) | |||
| if t == "f": | |||
| fermenter = cbpi.cache.get("fermenter").get(id) | |||
| result = map(self.convert_chart_data_to_json, cbpi.get_fermentation_controller(fermenter.logic).get("class").chart(fermenter)) | |||
| return json.dumps(result) | |||
| @route('/download/<file>') | |||
| @cbpi.nocache | |||
| def download(self, file): | |||
| if not self.check_filename(file): | |||
| return ('File Not Found', 404) | |||
| return send_from_directory('../logs', file, as_attachment=True, attachment_filename=file) | |||
| def check_filename(self, name): | |||
| import re | |||
| pattern = re.compile('^([A-Za-z0-9-_])+.log$') | |||
| return True if pattern.match(name) else False | |||
| @cbpi.initalizer() | |||
| def init(app): | |||
| """ | |||
| Initializer for the message module | |||
| :param app: the flask app | |||
| :return: None | |||
| """ | |||
| LogView.register(cbpi.app, route_base='/api/logs') | |||
| @@ -1,30 +1,46 @@ | |||
| import json | |||
| from flask_classy import FlaskView, route | |||
| from modules import cbpi | |||
| from modules.core.core import cbpi | |||
| class NotificationView(FlaskView): | |||
| @route('/', methods=['GET']) | |||
| def getMessages(self): | |||
| """ | |||
| Get all messages | |||
| :return: current messages | |||
| Get all Messages | |||
| --- | |||
| tags: | |||
| - notification | |||
| responses: | |||
| 200: | |||
| description: All messages | |||
| """ | |||
| return json.dumps(cbpi.cache["messages"]) | |||
| @route('/<id>', methods=['DELETE']) | |||
| def dismiss(self, id): | |||
| """ | |||
| Delete message from cache by id | |||
| :param id: message id to be deleted | |||
| :return: empty response HTTP 204 | |||
| Delete Message | |||
| --- | |||
| tags: | |||
| - notification | |||
| parameters: | |||
| - in: path | |||
| name: id | |||
| schema: | |||
| type: integer | |||
| required: true | |||
| description: Numeric ID of the message | |||
| responses: | |||
| 200: | |||
| description: Message deleted | |||
| """ | |||
| for idx, m in enumerate(cbpi.cache.get("messages", [])): | |||
| if (m.get("id") == id): | |||
| cbpi.cache["messages"].pop(idx) | |||
| return ('', 204) | |||
| @cbpi.event("MESSAGE", async=True) | |||
| #@cbpi.event("MESSAGE", async=True) | |||
| def messageEvent(message, **kwargs): | |||
| """ | |||
| React on message event. add the message to the cache and push the message to the clients | |||
| @@ -36,7 +52,7 @@ def messageEvent(message, **kwargs): | |||
| cbpi.cache["messages"].append(message) | |||
| cbpi.emit("NOTIFY", message) | |||
| @cbpi.initalizer(order=2) | |||
| @cbpi.addon.core.initializer(order=2) | |||
| def init(cbpi): | |||
| """ | |||
| Initializer for the message module | |||
| @@ -47,4 +63,4 @@ def init(cbpi): | |||
| msg = {"id": len(cbpi.cache["messages"]), "type": "info", "headline": "Support CraftBeerPi with your donation", "message": "You will find the PayPay Donation button in the system menu" , "read": False} | |||
| cbpi.cache["messages"].append(msg) | |||
| NotificationView.register(cbpi.app, route_base='/api/notification') | |||
| NotificationView.register(cbpi._app, route_base='/api/notification') | |||
| @@ -0,0 +1,138 @@ | |||
| import sys | |||
| from flask import request, send_from_directory, json | |||
| from importlib import import_module | |||
| from modules.core.core import cbpi | |||
| from git import Repo | |||
| import os | |||
| import requests | |||
| import yaml | |||
| import shutil | |||
| from flask_classy import FlaskView, route | |||
| modules = {} | |||
| class PluginView(FlaskView): | |||
| def merge(self, source, destination): | |||
| """ | |||
| Helper method to merge two dicts | |||
| :param source: | |||
| :param destination: | |||
| :return: | |||
| """ | |||
| for key, value in source.items(): | |||
| if isinstance(value, dict): | |||
| # get node or create one | |||
| node = destination.setdefault(key, {}) | |||
| self.merge(value, node) | |||
| else: | |||
| destination[key] = value | |||
| return destination | |||
| @route('/', methods=['GET']) | |||
| def get(self): | |||
| """ | |||
| Get Plugin List | |||
| --- | |||
| tags: | |||
| - plugin | |||
| responses: | |||
| 200: | |||
| description: List of all plugins | |||
| """ | |||
| response = requests.get("https://raw.githubusercontent.com/Manuel83/craftbeerpi-plugins/master/plugins.yaml") | |||
| self.api.cache["plugins"] = self.merge(yaml.load(response.text), self.api.cache["plugins"]) | |||
| for key, value in cbpi.cache["plugins"].iteritems(): | |||
| value["installed"] = os.path.isdir("./plugins/%s/" % (key)) | |||
| return json.dumps(cbpi.cache["plugins"]) | |||
| @route('/<name>', methods=['DELETE']) | |||
| def delete(self,name): | |||
| """ | |||
| Delete Plugin | |||
| --- | |||
| tags: | |||
| - plugin | |||
| parameters: | |||
| - in: path | |||
| name: name | |||
| schema: | |||
| type: string | |||
| required: true | |||
| description: Plugin name | |||
| responses: | |||
| 200: | |||
| description: Plugin deleted | |||
| """ | |||
| if os.path.isdir("./plugins/"+name) is False: | |||
| return ('Dir not found', 500) | |||
| shutil.rmtree("./plugins/"+name) | |||
| cbpi.notify("Plugin deleted", "Plugin %s deleted successfully" % name) | |||
| return ('', 204) | |||
| @route('/<name>/download', methods=['POST']) | |||
| def download(self, name): | |||
| """ | |||
| Download Plugin | |||
| --- | |||
| tags: | |||
| - plugin | |||
| parameters: | |||
| - in: path | |||
| name: name | |||
| schema: | |||
| type: string | |||
| required: true | |||
| description: Plugin name | |||
| responses: | |||
| 200: | |||
| description: Plugin downloaded | |||
| """ | |||
| plugin = self.api.cache["plugins"].get(name) | |||
| plugin["loading"] = True | |||
| if plugin is None: | |||
| return ('', 404) | |||
| try: | |||
| Repo.clone_from(plugin.get("repo_url"), "./modules/plugins/%s/" % (name)) | |||
| self.api.notify("Download successful", "Plugin %s downloaded successfully" % name) | |||
| finally: | |||
| plugin["loading"] = False | |||
| return ('', 204) | |||
| @route('/<name>/update', methods=['POST']) | |||
| def update(self, name): | |||
| """ | |||
| Pull Plugin Update | |||
| --- | |||
| tags: | |||
| - plugin | |||
| parameters: | |||
| - in: path | |||
| name: name | |||
| schema: | |||
| type: string | |||
| required: true | |||
| description: Plugin name | |||
| responses: | |||
| 200: | |||
| description: Plugin updated | |||
| """ | |||
| repo = Repo("./modules/plugins/%s/" % (name)) | |||
| o = repo.remotes.origin | |||
| info = o.pull() | |||
| self.api.notify("Plugin Updated", "Plugin %s updated successfully. Please restart the system" % name) | |||
| return ('', 204) | |||
| @cbpi.addon.core.initializer() | |||
| def init(cbpi): | |||
| cbpi.cache["plugins"] = {} | |||
| PluginView.api = cbpi | |||
| PluginView.register(cbpi._app, route_base='/api/plugin') | |||
| @@ -2,18 +2,28 @@ 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 modules.core.core import cbpi | |||
| from werkzeug.utils import secure_filename | |||
| import pprint | |||
| import time | |||
| import os | |||
| from modules.steps import Step,StepView | |||
| from modules.step import Step,StepView | |||
| import xml.etree.ElementTree | |||
| class BeerXMLImport(FlaskView): | |||
| BEER_XML_FILE = "./upload/beer.xml" | |||
| @route('/', methods=['GET']) | |||
| def get(self): | |||
| """ | |||
| Get BeerXML | |||
| --- | |||
| tags: | |||
| - beerxml | |||
| responses: | |||
| 200: | |||
| description: BeerXML file stored in CraftBeerPI | |||
| """ | |||
| 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") | |||
| @@ -31,6 +41,16 @@ class BeerXMLImport(FlaskView): | |||
| @route('/upload', methods=['POST']) | |||
| def upload_file(self): | |||
| """ | |||
| Upload BeerXML File | |||
| --- | |||
| tags: | |||
| - beerxml | |||
| responses: | |||
| 200: | |||
| description: BeerXML File Uploaded | |||
| """ | |||
| try: | |||
| if request.method == 'POST': | |||
| file = request.files['file'] | |||
| @@ -46,6 +66,23 @@ class BeerXMLImport(FlaskView): | |||
| @route('/<int:id>', methods=['POST']) | |||
| def load(self, id): | |||
| """ | |||
| Load Recipe from BeerXML | |||
| --- | |||
| tags: | |||
| - beerxml | |||
| parameters: | |||
| - in: path | |||
| name: id | |||
| schema: | |||
| type: integer | |||
| required: true | |||
| description: Recipe ID from BeerXML | |||
| responses: | |||
| 200: | |||
| description: Recipe loaed | |||
| """ | |||
| steps = self.getSteps(id) | |||
| name = self.getRecipeName(id) | |||
| @@ -103,8 +140,8 @@ class BeerXMLImport(FlaskView): | |||
| return steps | |||
| @cbpi.initalizer() | |||
| @cbpi.addon.core.initializer() | |||
| def init(cbpi): | |||
| BeerXMLImport.api = cbpi | |||
| BeerXMLImport.register(cbpi.app, route_base='/api/beerxml') | |||
| BeerXMLImport.register(cbpi._app, route_base='/api/beerxml') | |||
| @@ -2,18 +2,28 @@ 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 modules.core.core import cbpi | |||
| from werkzeug.utils import secure_filename | |||
| import pprint | |||
| import time | |||
| import os | |||
| from modules.steps import Step, StepView | |||
| from modules.step import Step, StepView | |||
| class KBH(FlaskView): | |||
| @route('/', methods=['GET']) | |||
| def get(self): | |||
| """ | |||
| Get all recipes from uploaded kleinerbrauhelfer database | |||
| --- | |||
| tags: | |||
| - kleinerbrauhelfer | |||
| responses: | |||
| 200: | |||
| description: Recipes from kleinerbrauhelfer database | |||
| """ | |||
| conn = None | |||
| try: | |||
| if not os.path.exists(self.api.app.config['UPLOAD_FOLDER'] + '/kbh.db'): | |||
| @@ -41,6 +51,16 @@ class KBH(FlaskView): | |||
| @route('/upload', methods=['POST']) | |||
| def upload_file(self): | |||
| """ | |||
| Upload KleinerBrauhelfer Database File | |||
| --- | |||
| tags: | |||
| - kleinerbrauhelfer | |||
| responses: | |||
| 200: | |||
| description: File uploaed | |||
| """ | |||
| try: | |||
| if request.method == 'POST': | |||
| file = request.files['file'] | |||
| @@ -57,6 +77,23 @@ class KBH(FlaskView): | |||
| @route('/<int:id>', methods=['POST']) | |||
| def load(self, id): | |||
| """ | |||
| Load Recipe from Kleinerbrauhelfer Database | |||
| --- | |||
| tags: | |||
| - kleinerbrauhelfer | |||
| parameters: | |||
| - in: path | |||
| name: id | |||
| schema: | |||
| type: integer | |||
| required: true | |||
| description: Numeric ID of recipe | |||
| responses: | |||
| 200: | |||
| description: Recipe loaded | |||
| """ | |||
| 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") | |||
| @@ -102,8 +139,8 @@ class KBH(FlaskView): | |||
| @cbpi.initalizer() | |||
| @cbpi.addon.core.initializer() | |||
| def init(cbpi): | |||
| KBH.api = cbpi | |||
| KBH.register(cbpi.app, route_base='/api/kbh') | |||
| KBH.register(cbpi._app, route_base='/api/kbh') | |||
| @@ -2,12 +2,12 @@ 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 modules.core.core import cbpi | |||
| from werkzeug.utils import secure_filename | |||
| import pprint | |||
| import time | |||
| import os | |||
| from modules.steps import Step,StepView | |||
| from modules.step import Step,StepView | |||
| import xml.etree.ElementTree | |||
| @@ -56,7 +56,7 @@ class RESTImport(FlaskView): | |||
| return ('', 204) | |||
| @cbpi.initalizer() | |||
| @cbpi.addon.core.initializer() | |||
| def init(cbpi): | |||
| RESTImport.api = cbpi | |||
| RESTImport.register(cbpi.app, route_base='/api/recipe/import/v1') | |||
| RESTImport.register(cbpi._app, route_base='/api/recipe/import/v1') | |||
| @@ -0,0 +1,67 @@ | |||
| import time | |||
| from flask_classy import route | |||
| from modules.core.core import cbpi | |||
| from modules.core.db import DBModel | |||
| from modules.core.baseview import BaseView | |||
| from modules.database.dbmodel import Sensor | |||
| class SensorView(BaseView): | |||
| model = Sensor | |||
| cache_key = "sensors" | |||
| @route('<int:id>/action/<method>', methods=["POST"]) | |||
| def action(self, id, method): | |||
| """ | |||
| Sensor Action | |||
| --- | |||
| tags: | |||
| - sensor | |||
| parameters: | |||
| - in: path | |||
| name: id | |||
| schema: | |||
| type: integer | |||
| required: true | |||
| description: Numeric ID of the sensor | |||
| - in: path | |||
| name: method | |||
| schema: | |||
| type: string | |||
| required: true | |||
| description: action method name | |||
| responses: | |||
| 200: | |||
| description: Sensor Action called | |||
| """ | |||
| cbpi.cache.get("sensors").get(id).instance.__getattribute__(method)() | |||
| return ('', 204) | |||
| def _post_post_callback(self, m): | |||
| cbpi.sensor.init_one(m.id) | |||
| def _post_put_callback(self, m): | |||
| cbpi.sensor.stop_one(m.id) | |||
| cbpi.sensor.init_one(m.id) | |||
| def _pre_delete_callback(self, m): | |||
| cbpi.sensor.stop_one(m.id) | |||
| @cbpi.addon.core.initializer(order=1000) | |||
| def init(cbpi): | |||
| SensorView.register(cbpi._app, route_base='/api/sensor') | |||
| SensorView.init_cache() | |||
| #@cbpi.backgroundtask(key="read_passiv_sensor", interval=5) | |||
| def read_passive_sensor(api): | |||
| """ | |||
| background process that reads all passive sensors in interval of 1 second | |||
| :return: None | |||
| """ | |||
| for key, value in cbpi.cache.get("sensors").iteritems(): | |||
| if value.mode == "P": | |||
| value.instance.read() | |||
| @@ -1,48 +0,0 @@ | |||
| import time | |||
| from flask_classy import route | |||
| from modules import DBModel, cbpi | |||
| from modules.core.baseview import BaseView | |||
| class Sensor(DBModel): | |||
| __fields__ = ["name","type", "config", "hide"] | |||
| __table_name__ = "sensor" | |||
| __json_fields__ = ["config"] | |||
| class SensorView(BaseView): | |||
| model = Sensor | |||
| cache_key = "sensors" | |||
| @route('<int:id>/action/<method>', methods=["POST"]) | |||
| def action(self, id, method): | |||
| 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): | |||
| cbpi.stop_sensor(m.id) | |||
| cbpi.init_sensor(m.id) | |||
| def _pre_delete_callback(self, m): | |||
| cbpi.stop_sensor(m.id) | |||
| @cbpi.initalizer(order=1000) | |||
| def init(cbpi): | |||
| 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(api): | |||
| """ | |||
| background process that reads all passive sensors in interval of 1 second | |||
| :return: None | |||
| """ | |||
| for key, value in cbpi.cache.get("sensors").iteritems(): | |||
| if value.mode == "P": | |||
| value.instance.read() | |||
| @@ -1,36 +1,35 @@ | |||
| from modules import cbpi | |||
| def getserial(): | |||
| cpuserial = "0000000000000000" | |||
| try: | |||
| f = open('/proc/cpuinfo','r') | |||
| for line in f: | |||
| if line[0:6]=='Serial': | |||
| cpuserial = line[10:26] | |||
| f.close() | |||
| except: | |||
| cpuserial = "0000000000000000" | |||
| return cpuserial | |||
| @cbpi.initalizer(order=9999) | |||
| def sendStats(cbpi): | |||
| try: | |||
| serial = getserial() | |||
| info = { | |||
| "id": serial, | |||
| "version": "3.0", | |||
| "kettle": len(cbpi.cache.get("kettle")), | |||
| "hardware": len(cbpi.cache.get("actors")), | |||
| "thermometer": "CBP3.0", | |||
| "hardware_control": "CBP3.0" | |||
| } | |||
| import requests | |||
| r = requests.post('http://statistics.craftbeerpi.com', json=info) | |||
| except Exception as e: | |||
| pass | |||
| from modules import cbpi | |||
| def getserial(): | |||
| cpuserial = "0000000000000000" | |||
| try: | |||
| f = open('/proc/cpuinfo','r') | |||
| for line in f: | |||
| if line[0:6]=='Serial': | |||
| cpuserial = line[10:26] | |||
| f.close() | |||
| except: | |||
| cpuserial = "0000000000000000" | |||
| return cpuserial | |||
| @cbpi.initalizer(order=9999) | |||
| def sendStats(cbpi): | |||
| try: | |||
| serial = getserial() | |||
| info = { | |||
| "id": serial, | |||
| "version": "3.1", | |||
| "kettle": len(cbpi.cache.get("kettle")), | |||
| "hardware": len(cbpi.cache.get("actors")), | |||
| "thermometer": "CBP3.0", | |||
| "hardware_control": "CBP3.0" | |||
| } | |||
| import requests | |||
| #r = requests.post('http://statistics.craftbeerpi.com', json=info) | |||
| except Exception as e: | |||
| pass | |||
| @@ -1,239 +1,241 @@ | |||
| import time | |||
| from flask import json, request | |||
| from flask_classy import route | |||
| from modules import DBModel, cbpi, get_db | |||
| from modules.core.baseview import BaseView | |||
| class Step(DBModel): | |||
| __fields__ = ["name","type", "stepstate", "state", "start", "end", "order", "config"] | |||
| __table_name__ = "step" | |||
| __json_fields__ = ["config", "stepstate"] | |||
| __order_by__ = "order" | |||
| __as_array__ = True | |||
| @classmethod | |||
| def get_max_order(cls): | |||
| cur = get_db().cursor() | |||
| cur.execute("SELECT max(step.'order') as 'order' FROM %s" % cls.__table_name__) | |||
| r = cur.fetchone() | |||
| return r.get("order") | |||
| @classmethod | |||
| def get_by_state(cls, state, order=True): | |||
| cur = get_db().cursor() | |||
| 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) | |||
| else: | |||
| return None | |||
| @classmethod | |||
| def delete_all(cls): | |||
| cur = get_db().cursor() | |||
| cur.execute("DELETE FROM %s" % cls.__table_name__) | |||
| get_db().commit() | |||
| @classmethod | |||
| def reset_all_steps(cls): | |||
| cur = get_db().cursor() | |||
| cur.execute("UPDATE %s SET state = 'I', stepstate = NULL , start = NULL, end = NULL " % cls.__table_name__) | |||
| get_db().commit() | |||
| @classmethod | |||
| def update_state(cls, id, state): | |||
| cur = get_db().cursor() | |||
| cur.execute("UPDATE %s SET state = ? WHERE id =?" % cls.__table_name__, (state, id)) | |||
| get_db().commit() | |||
| @classmethod | |||
| def update_step_state(cls, id, state): | |||
| cur = get_db().cursor() | |||
| 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): | |||
| 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() | |||
| cbpi.emit("UPDATE_ALL_STEPS", self.model.get_all()) | |||
| return ('', 204) | |||
| @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() | |||
| self.stop_step() | |||
| cbpi.emit("UPDATE_ALL_STEPS", self.model.get_all()) | |||
| return ('', 204) | |||
| def stop_step(self): | |||
| ''' | |||
| stop active step | |||
| :return: | |||
| ''' | |||
| step = cbpi.cache.get("active_step") | |||
| cbpi.cache["active_step"] = None | |||
| if step is not None: | |||
| step.finish() | |||
| @route('/reset/current', methods=['POST']) | |||
| def resetCurrentStep(self): | |||
| ''' | |||
| Reset current step | |||
| :return: | |||
| ''' | |||
| step = cbpi.cache.get("active_step") | |||
| if step is not None: | |||
| step.reset() | |||
| if step.is_dirty(): | |||
| state = {} | |||
| for field in step.managed_fields: | |||
| state[field] = step.__getattribute__(field) | |||
| Step.update_step_state(step.id, state) | |||
| step.reset_dirty() | |||
| cbpi.emit("UPDATE_ALL_STEPS", self.model.get_all()) | |||
| 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: | |||
| # if type not found | |||
| return | |||
| # copy config to stepstate | |||
| # init step | |||
| cfg = step.config.copy() | |||
| 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 | |||
| @route('/next', methods=['POST']) | |||
| @route('/start', methods=['POST']) | |||
| def start(self): | |||
| active = Step.get_by_state("A") | |||
| inactive = Step.get_by_state('I') | |||
| if (active is not None): | |||
| active.state = 'D' | |||
| active.end = int(time.time()) | |||
| self.stop_step() | |||
| Step.update(**active.__dict__) | |||
| if (inactive is not None): | |||
| self.init_step(inactive) | |||
| inactive.state = 'A' | |||
| inactive.stepstate = inactive.config | |||
| inactive.start = int(time.time()) | |||
| Step.update(**inactive.__dict__) | |||
| else: | |||
| cbpi.log_action("Brewing Finished") | |||
| cbpi.notify("Brewing Finished", "You are done!", timeout=None) | |||
| cbpi.emit("UPDATE_ALL_STEPS", Step.get_all()) | |||
| return ('', 204) | |||
| def get_manged_fields_as_array(type_cfg): | |||
| result = [] | |||
| for f in type_cfg.get("properties"): | |||
| result.append(f.get("name")) | |||
| return result | |||
| @cbpi.try_catch(None) | |||
| def init_after_startup(): | |||
| ''' | |||
| Restart after startup. Check is a step is in state A and reinitialize | |||
| :return: None | |||
| ''' | |||
| step = Step.get_by_state('A') | |||
| # We have an active step | |||
| if step is not None: | |||
| # get the type | |||
| type_cfg = cbpi.cache.get("step_types").get(step.type) | |||
| 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, 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): | |||
| StepView.register(cbpi.app, route_base='/api/step') | |||
| def get_all(): | |||
| with cbpi.app.app_context(): | |||
| return Step.get_all() | |||
| with cbpi.app.app_context(): | |||
| init_after_startup() | |||
| cbpi.add_cache_callback("steps", get_all) | |||
| @cbpi.backgroundtask(key="step_task", interval=0.1) | |||
| def execute_step(api): | |||
| ''' | |||
| Background job which executes the step | |||
| :return: | |||
| ''' | |||
| with cbpi.app.app_context(): | |||
| 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) | |||
| Step.update_step_state(step.id, state) | |||
| step.reset_dirty() | |||
| cbpi.emit("UPDATE_ALL_STEPS", Step.get_all()) | |||
| if step.n is True: | |||
| StepView().start() | |||
| cbpi.emit("UPDATE_ALL_STEPS", Step.get_all()) | |||
| import time | |||
| from flask import json, request | |||
| from flask_classy import route | |||
| from modules.core.db import DBModel | |||
| from modules.core.baseview import BaseView | |||
| from modules.core.core import cbpi | |||
| from modules.database.dbmodel import Step | |||
| class StepView(BaseView): | |||
| model = Step | |||
| 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): | |||
| """ | |||
| Sort all steps | |||
| --- | |||
| tags: | |||
| - steps | |||
| responses: | |||
| 204: | |||
| description: Steps sorted. Update delivered via web socket | |||
| """ | |||
| Step.sort(request.json) | |||
| cbpi.ws_emit("UPDATE_ALL_STEPS", self.model.get_all()) | |||
| return ('', 204) | |||
| @route('/', methods=["DELETE"]) | |||
| def deleteAll(self): | |||
| """ | |||
| Delete all Steps | |||
| --- | |||
| tags: | |||
| - steps | |||
| responses: | |||
| 204: | |||
| description: All steps deleted | |||
| """ | |||
| self.model.delete_all() | |||
| self.api.emit("ALL_BREWING_STEPS_DELETED") | |||
| cbpi.ws_emit("UPDATE_ALL_STEPS", self.model.get_all()) | |||
| return ('', 204) | |||
| @route('/action/<method>', methods=["POST"]) | |||
| def action(self, method): | |||
| """ | |||
| Call Step Action | |||
| --- | |||
| tags: | |||
| - steps | |||
| responses: | |||
| 204: | |||
| description: Step action called | |||
| """ | |||
| self.api.emit("BREWING_STEP_ACTION_INVOKED", method=method) | |||
| cbpi.cache["active_step"].__getattribute__(method)() | |||
| return ('', 204) | |||
| @route('/reset', methods=["POST"]) | |||
| def reset(self): | |||
| """ | |||
| Reset All Steps | |||
| --- | |||
| tags: | |||
| - steps | |||
| responses: | |||
| 200: | |||
| description: Steps reseted | |||
| """ | |||
| self.model.reset_all_steps() | |||
| self.stop_step() | |||
| self.api.emit("ALL_BREWING_STEPS_RESET") | |||
| cbpi.ws_emit("UPDATE_ALL_STEPS", self.model.get_all()) | |||
| return ('', 204) | |||
| def stop_step(self): | |||
| ''' | |||
| stop active step | |||
| :return: | |||
| ''' | |||
| step = cbpi.cache.get("active_step") | |||
| cbpi.cache["active_step"] = None | |||
| self.api.emit("BREWING_STEPS_STOP") | |||
| if step is not None: | |||
| step.finish() | |||
| @route('/reset/current', methods=['POST']) | |||
| def resetCurrentStep(self): | |||
| """ | |||
| Reset current Steps | |||
| --- | |||
| tags: | |||
| - steps | |||
| responses: | |||
| 200: | |||
| description: Current Steps reseted | |||
| """ | |||
| step = cbpi.cache.get("active_step") | |||
| if step is not None: | |||
| step.reset() | |||
| if step.is_dirty(): | |||
| state = {} | |||
| for field in step.managed_fields: | |||
| state[field] = step.__getattribute__(field) | |||
| Step.update_step_state(step.id, state) | |||
| step.reset_dirty() | |||
| self.api.emit("BREWING_STEPS_RESET_CURRENT") | |||
| cbpi.ws_emit("UPDATE_ALL_STEPS", self.model.get_all()) | |||
| return ('', 204) | |||
| def init_step(self, step): | |||
| cbpi.brewing.log_action("Start Step %s" % step.name) | |||
| type_cfg = cbpi.cache.get("step_types").get(step.type) | |||
| if type_cfg is None: | |||
| # if type not found | |||
| return | |||
| # copy config to stepstate | |||
| # init step | |||
| cfg = step.config.copy() | |||
| 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 | |||
| @route('/next', methods=['POST']) | |||
| @route('/start', methods=['POST']) | |||
| def start(self): | |||
| """ | |||
| Next Step | |||
| --- | |||
| tags: | |||
| - steps | |||
| responses: | |||
| 200: | |||
| description: Next Step | |||
| """ | |||
| active = Step.get_by_state("A") | |||
| inactive = Step.get_by_state('I') | |||
| if (active is not None): | |||
| active.state = 'D' | |||
| active.end = int(time.time()) | |||
| self.stop_step() | |||
| Step.update(**active.__dict__) | |||
| self.api.emit("BREWING_STEP_DONE") | |||
| if (inactive is not None): | |||
| self.init_step(inactive) | |||
| inactive.state = 'A' | |||
| inactive.stepstate = inactive.config | |||
| inactive.start = int(time.time()) | |||
| Step.update(**inactive.__dict__) | |||
| self.api.emit("BREWING_STEP_STARTED") | |||
| else: | |||
| cbpi.brewing.log_action("Brewing Finished") | |||
| self.api.emit("BREWING_FINISHED") | |||
| cbpi.notify("Brewing Finished", "You are done!", timeout=None) | |||
| cbpi.ws_emit("UPDATE_ALL_STEPS", Step.get_all()) | |||
| return ('', 204) | |||
| def get_manged_fields_as_array(type_cfg): | |||
| result = [] | |||
| for f in type_cfg.get("properties"): | |||
| result.append(f.get("name")) | |||
| return result | |||
| def init_after_startup(): | |||
| ''' | |||
| Restart after startup. Check is a step is in state A and reinitialize | |||
| :return: None | |||
| ''' | |||
| step = Step.get_by_state('A') | |||
| # We have an active step | |||
| if step is not None: | |||
| # get the type | |||
| type_cfg = cbpi.cache.get("step_types").get(step.type) | |||
| 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, managed_fields=get_manged_fields_as_array(type_cfg))) | |||
| instance = type_cfg.get("class")(**cfg) | |||
| instance.init() | |||
| cbpi.cache["active_step"] = instance | |||
| @cbpi.addon.core.initializer(order=2000) | |||
| def init(cbpi): | |||
| StepView.register(cbpi._app, route_base='/api/step') | |||
| def get_all(): | |||
| with cbpi._app.app_context(): | |||
| return Step.get_all() | |||
| with cbpi._app.app_context(): | |||
| init_after_startup() | |||
| cbpi.add_cache_callback("steps", get_all) | |||
| @cbpi.addon.core.backgroundjob(key="step_task", interval=0.1) | |||
| def execute_step(api): | |||
| ''' | |||
| Background job which executes the step | |||
| :return: | |||
| ''' | |||
| with cbpi._app.app_context(): | |||
| 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) | |||
| Step.update_step_state(step.id, state) | |||
| step.reset_dirty() | |||
| cbpi.ws_emit("UPDATE_ALL_STEPS", Step.get_all()) | |||
| if step.n is True: | |||
| StepView().start() | |||
| cbpi.ws_emit("UPDATE_ALL_STEPS", Step.get_all()) | |||
| @@ -1 +1,152 @@ | |||
| import endpoints | |||
| import yaml | |||
| from flask import json, url_for, Response | |||
| from flask_classy import FlaskView, route | |||
| from git import Repo, Git | |||
| from modules.core.core import cbpi | |||
| import pprint | |||
| import time | |||
| class SystemView(FlaskView): | |||
| def doShutdown(self): | |||
| time.sleep(5) | |||
| from subprocess import call | |||
| call("halt") | |||
| @route('/shutdown', methods=['POST']) | |||
| def shutdown(self): | |||
| """ | |||
| System Shutdown | |||
| --- | |||
| tags: | |||
| - system | |||
| responses: | |||
| 200: | |||
| description: Shutdown triggered | |||
| """ | |||
| self.doShutdown() | |||
| return ('', 204) | |||
| def doReboot(self): | |||
| time.sleep(5) | |||
| from subprocess import call | |||
| call("reboot") | |||
| @route('/reboot', methods=['POST']) | |||
| def reboot(self): | |||
| """ | |||
| System Reboot | |||
| --- | |||
| tags: | |||
| - system | |||
| responses: | |||
| 200: | |||
| description: Reboot triggered | |||
| """ | |||
| self.doReboot() | |||
| return ('', 204) | |||
| @route('/tags/<name>', methods=['GET']) | |||
| def checkout_tag(self,name): | |||
| repo = Repo('./') | |||
| repo.git.reset('--hard') | |||
| o = repo.remotes.origin | |||
| o.fetch() | |||
| g = Git('./') | |||
| g.checkout(name) | |||
| cbpi.notify("Checkout successful", "Please restart the system") | |||
| return ('', 204) | |||
| @route('/git/status', methods=['GET']) | |||
| def git_status(self): | |||
| """ | |||
| Check for GIT status | |||
| --- | |||
| tags: | |||
| - system | |||
| responses: | |||
| 200: | |||
| description: Git Status | |||
| """ | |||
| repo = Repo('./') | |||
| o = repo.remotes.origin | |||
| o.fetch() | |||
| # Tags | |||
| tags = [] | |||
| for t in repo.tags: | |||
| tags.append({"name": t.name, "commit": str(t.commit), "date": t.commit.committed_date, | |||
| "committer": t.commit.committer.name, "message": t.commit.message}) | |||
| try: | |||
| branch_name = repo.active_branch.name | |||
| # test1 | |||
| except: | |||
| branch_name = None | |||
| changes = [] | |||
| commits_behind = repo.iter_commits('master..origin/master') | |||
| for c in list(commits_behind): | |||
| changes.append({"committer": c.committer.name, "message": c.message}) | |||
| return json.dumps({"tags": tags, "headcommit": str(repo.head.commit), "branchname": branch_name, | |||
| "master": {"changes": changes}}) | |||
| @route('/check_update', methods=['GET']) | |||
| def check_update(self): | |||
| """ | |||
| Check for GIT update | |||
| --- | |||
| tags: | |||
| - system | |||
| responses: | |||
| 200: | |||
| description: Git Changes | |||
| """ | |||
| repo = Repo('./') | |||
| o = repo.remotes.origin | |||
| o.fetch() | |||
| changes = [] | |||
| commits_behind = repo.iter_commits('master..origin/master') | |||
| for c in list(commits_behind): | |||
| changes.append({"committer": c.committer.name, "message": c.message}) | |||
| return json.dumps(changes) | |||
| @route('/git/pull', methods=['POST']) | |||
| def update(self): | |||
| """ | |||
| System Update | |||
| --- | |||
| tags: | |||
| - system | |||
| responses: | |||
| 200: | |||
| description: Git Pull Triggered | |||
| """ | |||
| repo = Repo('./') | |||
| o = repo.remotes.origin | |||
| info = o.pull() | |||
| cbpi.notify("Pull successful", "The lasted updated was downloaded. Please restart the system") | |||
| return ('', 204) | |||
| @route('/dump', methods=['GET']) | |||
| def dump(self): | |||
| """ | |||
| Dump Cache | |||
| --- | |||
| tags: | |||
| - system | |||
| responses: | |||
| 200: | |||
| description: CraftBeerPi System Cache | |||
| """ | |||
| return json.dumps(cbpi.cache) | |||
| @cbpi.addon.core.initializer() | |||
| def init(cbpi): | |||
| SystemView.api = cbpi | |||
| SystemView.register(cbpi._app, route_base='/api/system') | |||
| @@ -1,145 +0,0 @@ | |||
| import yaml | |||
| from flask import json, url_for, Response | |||
| from flask_classy import FlaskView, route | |||
| from git import Repo, Git | |||
| from modules.app_config import cbpi | |||
| import pprint | |||
| import time | |||
| class SystemView(FlaskView): | |||
| def doShutdown(self): | |||
| time.sleep(5) | |||
| from subprocess import call | |||
| call("halt") | |||
| @route('/shutdown', methods=['POST']) | |||
| def shutdown(self): | |||
| """ | |||
| Shutdown hook | |||
| :return: HTTP 204 | |||
| """ | |||
| self.doShutdown() | |||
| return ('', 204) | |||
| def doReboot(self): | |||
| time.sleep(5) | |||
| from subprocess import call | |||
| call("reboot") | |||
| @route('/reboot', methods=['POST']) | |||
| def reboot(self): | |||
| """ | |||
| Reboot hook | |||
| :return: HTTP 204 | |||
| """ | |||
| self.doReboot() | |||
| return ('', 204) | |||
| @route('/tags/<name>', methods=['GET']) | |||
| def checkout_tag(self,name): | |||
| repo = Repo('./') | |||
| repo.git.reset('--hard') | |||
| o = repo.remotes.origin | |||
| o.fetch() | |||
| g = Git('./') | |||
| g.checkout(name) | |||
| cbpi.notify("Checkout successful", "Please restart the system") | |||
| return ('', 204) | |||
| @route('/git/status', methods=['GET']) | |||
| def git_status(self): | |||
| repo = Repo('./') | |||
| o = repo.remotes.origin | |||
| o.fetch() | |||
| # Tags | |||
| tags = [] | |||
| for t in repo.tags: | |||
| tags.append({"name": t.name, "commit": str(t.commit), "date": t.commit.committed_date, | |||
| "committer": t.commit.committer.name, "message": t.commit.message}) | |||
| try: | |||
| branch_name = repo.active_branch.name | |||
| # test1 | |||
| except: | |||
| branch_name = None | |||
| changes = [] | |||
| commits_behind = repo.iter_commits('master..origin/master') | |||
| for c in list(commits_behind): | |||
| changes.append({"committer": c.committer.name, "message": c.message}) | |||
| return json.dumps({"tags": tags, "headcommit": str(repo.head.commit), "branchname": branch_name, | |||
| "master": {"changes": changes}}) | |||
| @route('/check_update', methods=['GET']) | |||
| def check_update(self): | |||
| repo = Repo('./') | |||
| o = repo.remotes.origin | |||
| o.fetch() | |||
| changes = [] | |||
| commits_behind = repo.iter_commits('master..origin/master') | |||
| for c in list(commits_behind): | |||
| changes.append({"committer": c.committer.name, "message": c.message}) | |||
| return json.dumps(changes) | |||
| @route('/git/pull', methods=['POST']) | |||
| def update(self): | |||
| repo = Repo('./') | |||
| o = repo.remotes.origin | |||
| info = o.pull() | |||
| cbpi.notify("Pull successful", "The lasted updated was downloaded. Please restart the system") | |||
| return ('', 204) | |||
| @route('/dump', methods=['GET']) | |||
| 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): | |||
| SystemView.api = cbpi | |||
| SystemView.register(cbpi.app, route_base='/api/system') | |||
| @@ -1 +1,23 @@ | |||
| import endpoints | |||
| from flask import Blueprint | |||
| from modules.core.core import cbpi | |||
| react = Blueprint('ui', __name__, template_folder='templates', static_folder='static') | |||
| @cbpi.addon.core.initializer(order=10) | |||
| def init(cbpi): | |||
| cbpi._app.register_blueprint(react, url_prefix='/ui') | |||
| @react.route('/', methods=["GET"]) | |||
| def index(): | |||
| return react.send_static_file("index.html") | |||
| @@ -1,25 +0,0 @@ | |||
| from flask import Blueprint | |||
| from modules import cbpi | |||
| react = Blueprint('react', __name__, template_folder='templates', static_folder='static') | |||
| @cbpi.initalizer(order=10) | |||
| def init(cbpi): | |||
| cbpi.app.register_blueprint(react, url_prefix='/ui') | |||
| @react.route('/', methods=["GET"]) | |||
| def index(): | |||
| return react.send_static_file("index.html") | |||
| @@ -17,5 +17,6 @@ | |||
| <div id="root" ></div> | |||
| <script src="static/bundle.js" type="text/javascript"></script> | |||
| </body> | |||
| </html> | |||
| @@ -1,11 +1,28 @@ | |||
| #!/usr/bin/env python | |||
| from modules import socketio, app, cbpi | |||
| from modules.core.core import * | |||
| try: | |||
| port = int(cbpi.get_config_parameter('port', '5000')) | |||
| except ValueError: | |||
| port = 5000 | |||
| cbpi = CraftBeerPI() | |||
| socketio.run(app, host='0.0.0.0', port=port) | |||
| addon = cbpi.addon | |||
| from modules.buzzer import * | |||
| from modules.config import * | |||
| from modules.core.login import * | |||
| from modules.system import * | |||
| from modules.ui import * | |||
| from modules.step import * | |||
| from modules.kettle import * | |||
| from modules.actor import * | |||
| from modules.plugin import * | |||
| from modules.logs import * | |||
| from modules.notification import * | |||
| from modules.sensor import * | |||
| from modules.recipe_import import * | |||
| from modules.fermenter import * | |||
| from modules.action import * | |||
| from modules.base_plugins.actor import * | |||
| from modules.base_plugins.sensor import * | |||
| from modules.base_plugins.steps import * | |||
| cbpi.run() | |||