- 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 import request | ||||
| from flask_classy import route | 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.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): | class FermenterView(BaseView): | ||||
| model = Fermenter | model = Fermenter | ||||
| cache_key = "fermenter" | cache_key = "fermenter" | ||||
| @@ -86,7 +35,7 @@ class FermenterView(BaseView): | |||||
| temp = float(temp) | temp = float(temp) | ||||
| cbpi.cache.get(self.cache_key)[id].target_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__) | 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) | return ('', 204) | ||||
| @route('/<int:id>/brewname', methods=['POST']) | @route('/<int:id>/brewname', methods=['POST']) | ||||
| @@ -95,7 +44,7 @@ class FermenterView(BaseView): | |||||
| brewname = data.get("brewname") | brewname = data.get("brewname") | ||||
| cbpi.cache.get(self.cache_key)[id].brewname = brewname | cbpi.cache.get(self.cache_key)[id].brewname = brewname | ||||
| self.model.update(**self.api.cache.get(self.cache_key)[id].__dict__) | 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) | return ('', 204) | ||||
| @classmethod | @classmethod | ||||
| @@ -119,7 +68,7 @@ class FermenterView(BaseView): | |||||
| cbpi.cache.get(self.cache_key)[id].steps.append(f) | 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) | return ('', 204) | ||||
| @@ -141,7 +90,7 @@ class FermenterView(BaseView): | |||||
| FermenterStep.update(**s.__dict__) | FermenterStep.update(**s.__dict__) | ||||
| break | 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) | return ('', 204) | ||||
| @route('/<int:id>/step/<int:stepid>', methods=["DELETE"]) | @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] | del cbpi.cache.get(self.cache_key)[id].steps[idx] | ||||
| FermenterStep.delete(s.id) | FermenterStep.delete(s.id) | ||||
| break | 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) | return ('', 204) | ||||
| @route('/<int:id>/start', methods=['POST']) | @route('/<int:id>/start', methods=['POST']) | ||||
| @@ -179,7 +128,8 @@ class FermenterView(BaseView): | |||||
| if inactive is not None: | if inactive is not None: | ||||
| fermenter = self.get_fermenter(inactive.fermenter_id) | 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.state = 'A' | ||||
| inactive.start = time.time() | inactive.start = time.time() | ||||
| @@ -190,7 +140,7 @@ class FermenterView(BaseView): | |||||
| cbpi.cache["fermenter_task"][id] = inactive | 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) | return ('', 204) | ||||
| @route('/<int:id>/reset', methods=["POST"]) | @route('/<int:id>/reset', methods=["POST"]) | ||||
| @@ -202,7 +152,7 @@ class FermenterView(BaseView): | |||||
| if id in cbpi.cache["fermenter_task"]: | if id in cbpi.cache["fermenter_task"]: | ||||
| del cbpi.cache["fermenter_task"][id] | 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) | return ('', 204) | ||||
| @route('/<int:id>/automatic', methods=['POST']) | @route('/<int:id>/automatic', methods=['POST']) | ||||
| @@ -216,21 +166,23 @@ class FermenterView(BaseView): | |||||
| cfg = fermenter.config.copy() | cfg = fermenter.config.copy() | ||||
| cfg.update( | cfg.update( | ||||
| dict(api=cbpi, fermenter_id=fermenter.id, heater=fermenter.heater, sensor=fermenter.sensor)) | 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() | instance.init() | ||||
| fermenter.instance = instance | fermenter.instance = instance | ||||
| def run(instance): | def run(instance): | ||||
| instance.run() | 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 | 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: | else: | ||||
| # Stop controller | # Stop controller | ||||
| fermenter.instance.stop() | fermenter.instance.stop() | ||||
| fermenter.state = not fermenter.state | 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: | except Exception as e: | ||||
| print e | print e | ||||
| @@ -255,7 +207,7 @@ class FermenterView(BaseView): | |||||
| step.timer_start = target_time | 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): | def check_step(self): | ||||
| for key, value in cbpi.cache["fermenter_task"].iteritems(): | for key, value in cbpi.cache["fermenter_task"].iteritems(): | ||||
| @@ -282,35 +234,33 @@ class FermenterView(BaseView): | |||||
| pass | 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 | background process that reads all passive sensors in interval of 1 second | ||||
| :return: None | :return: None | ||||
| """ | """ | ||||
| result = {} | |||||
| for key, value in cbpi.cache.get("fermenter").iteritems(): | 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() | 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() | instance.check_step() | ||||
| def init_active_steps(): | 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): | 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() | 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 | import json | ||||
| from flask_classy import FlaskView, route | from flask_classy import FlaskView, route | ||||
| from modules import cbpi | |||||
| from modules.core.core import cbpi | |||||
| class NotificationView(FlaskView): | class NotificationView(FlaskView): | ||||
| @route('/', methods=['GET']) | @route('/', methods=['GET']) | ||||
| def getMessages(self): | 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"]) | return json.dumps(cbpi.cache["messages"]) | ||||
| @route('/<id>', methods=['DELETE']) | @route('/<id>', methods=['DELETE']) | ||||
| def dismiss(self, id): | 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", [])): | for idx, m in enumerate(cbpi.cache.get("messages", [])): | ||||
| if (m.get("id") == id): | if (m.get("id") == id): | ||||
| cbpi.cache["messages"].pop(idx) | cbpi.cache["messages"].pop(idx) | ||||
| return ('', 204) | return ('', 204) | ||||
| @cbpi.event("MESSAGE", async=True) | |||||
| #@cbpi.event("MESSAGE", async=True) | |||||
| def messageEvent(message, **kwargs): | def messageEvent(message, **kwargs): | ||||
| """ | """ | ||||
| React on message event. add the message to the cache and push the message to the clients | 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.cache["messages"].append(message) | ||||
| cbpi.emit("NOTIFY", message) | cbpi.emit("NOTIFY", message) | ||||
| @cbpi.initalizer(order=2) | |||||
| @cbpi.addon.core.initializer(order=2) | |||||
| def init(cbpi): | def init(cbpi): | ||||
| """ | """ | ||||
| Initializer for the message module | 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} | 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) | 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 flask_classy import FlaskView, route | ||||
| from git import Repo, Git | from git import Repo, Git | ||||
| import sqlite3 | import sqlite3 | ||||
| from modules.app_config import cbpi | |||||
| from modules.core.core import cbpi | |||||
| from werkzeug.utils import secure_filename | from werkzeug.utils import secure_filename | ||||
| import pprint | import pprint | ||||
| import time | import time | ||||
| import os | import os | ||||
| from modules.steps import Step,StepView | |||||
| from modules.step import Step,StepView | |||||
| import xml.etree.ElementTree | import xml.etree.ElementTree | ||||
| class BeerXMLImport(FlaskView): | class BeerXMLImport(FlaskView): | ||||
| BEER_XML_FILE = "./upload/beer.xml" | BEER_XML_FILE = "./upload/beer.xml" | ||||
| @route('/', methods=['GET']) | @route('/', methods=['GET']) | ||||
| def get(self): | def get(self): | ||||
| """ | |||||
| Get BeerXML | |||||
| --- | |||||
| tags: | |||||
| - beerxml | |||||
| responses: | |||||
| 200: | |||||
| description: BeerXML file stored in CraftBeerPI | |||||
| """ | |||||
| if not os.path.exists(self.BEER_XML_FILE): | if not os.path.exists(self.BEER_XML_FILE): | ||||
| self.api.notify(headline="File Not Found", message="Please upload a Beer.xml File", | self.api.notify(headline="File Not Found", message="Please upload a Beer.xml File", | ||||
| type="danger") | type="danger") | ||||
| @@ -31,6 +41,16 @@ class BeerXMLImport(FlaskView): | |||||
| @route('/upload', methods=['POST']) | @route('/upload', methods=['POST']) | ||||
| def upload_file(self): | def upload_file(self): | ||||
| """ | |||||
| Upload BeerXML File | |||||
| --- | |||||
| tags: | |||||
| - beerxml | |||||
| responses: | |||||
| 200: | |||||
| description: BeerXML File Uploaded | |||||
| """ | |||||
| try: | try: | ||||
| if request.method == 'POST': | if request.method == 'POST': | ||||
| file = request.files['file'] | file = request.files['file'] | ||||
| @@ -46,6 +66,23 @@ class BeerXMLImport(FlaskView): | |||||
| @route('/<int:id>', methods=['POST']) | @route('/<int:id>', methods=['POST']) | ||||
| def load(self, id): | 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) | steps = self.getSteps(id) | ||||
| name = self.getRecipeName(id) | name = self.getRecipeName(id) | ||||
| @@ -103,8 +140,8 @@ class BeerXMLImport(FlaskView): | |||||
| return steps | return steps | ||||
| @cbpi.initalizer() | |||||
| @cbpi.addon.core.initializer() | |||||
| def init(cbpi): | def init(cbpi): | ||||
| BeerXMLImport.api = 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 flask_classy import FlaskView, route | ||||
| from git import Repo, Git | from git import Repo, Git | ||||
| import sqlite3 | import sqlite3 | ||||
| from modules.app_config import cbpi | |||||
| from modules.core.core import cbpi | |||||
| from werkzeug.utils import secure_filename | from werkzeug.utils import secure_filename | ||||
| import pprint | import pprint | ||||
| import time | import time | ||||
| import os | import os | ||||
| from modules.steps import Step, StepView | |||||
| from modules.step import Step, StepView | |||||
| class KBH(FlaskView): | class KBH(FlaskView): | ||||
| @route('/', methods=['GET']) | @route('/', methods=['GET']) | ||||
| def get(self): | def get(self): | ||||
| """ | |||||
| Get all recipes from uploaded kleinerbrauhelfer database | |||||
| --- | |||||
| tags: | |||||
| - kleinerbrauhelfer | |||||
| responses: | |||||
| 200: | |||||
| description: Recipes from kleinerbrauhelfer database | |||||
| """ | |||||
| conn = None | conn = None | ||||
| try: | try: | ||||
| if not os.path.exists(self.api.app.config['UPLOAD_FOLDER'] + '/kbh.db'): | if not os.path.exists(self.api.app.config['UPLOAD_FOLDER'] + '/kbh.db'): | ||||
| @@ -41,6 +51,16 @@ class KBH(FlaskView): | |||||
| @route('/upload', methods=['POST']) | @route('/upload', methods=['POST']) | ||||
| def upload_file(self): | def upload_file(self): | ||||
| """ | |||||
| Upload KleinerBrauhelfer Database File | |||||
| --- | |||||
| tags: | |||||
| - kleinerbrauhelfer | |||||
| responses: | |||||
| 200: | |||||
| description: File uploaed | |||||
| """ | |||||
| try: | try: | ||||
| if request.method == 'POST': | if request.method == 'POST': | ||||
| file = request.files['file'] | file = request.files['file'] | ||||
| @@ -57,6 +77,23 @@ class KBH(FlaskView): | |||||
| @route('/<int:id>', methods=['POST']) | @route('/<int:id>', methods=['POST']) | ||||
| def load(self, id): | 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") | mashstep_type = cbpi.get_config_parameter("step_mash", "MashStep") | ||||
| mashinstep_type = cbpi.get_config_parameter("step_mashin", "MashInStep") | mashinstep_type = cbpi.get_config_parameter("step_mashin", "MashInStep") | ||||
| chilstep_type = cbpi.get_config_parameter("step_chil", "ChilStep") | 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): | def init(cbpi): | ||||
| KBH.api = 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 flask_classy import FlaskView, route | ||||
| from git import Repo, Git | from git import Repo, Git | ||||
| import sqlite3 | import sqlite3 | ||||
| from modules.app_config import cbpi | |||||
| from modules.core.core import cbpi | |||||
| from werkzeug.utils import secure_filename | from werkzeug.utils import secure_filename | ||||
| import pprint | import pprint | ||||
| import time | import time | ||||
| import os | import os | ||||
| from modules.steps import Step,StepView | |||||
| from modules.step import Step,StepView | |||||
| import xml.etree.ElementTree | import xml.etree.ElementTree | ||||
| @@ -56,7 +56,7 @@ class RESTImport(FlaskView): | |||||
| return ('', 204) | return ('', 204) | ||||
| @cbpi.initalizer() | |||||
| @cbpi.addon.core.initializer() | |||||
| def init(cbpi): | def init(cbpi): | ||||
| RESTImport.api = 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> | <div id="root" ></div> | ||||
| <script src="static/bundle.js" type="text/javascript"></script> | <script src="static/bundle.js" type="text/javascript"></script> | ||||
| </body> | </body> | ||||
| </html> | </html> | ||||
| @@ -1,11 +1,28 @@ | |||||
| #!/usr/bin/env python | #!/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() | |||||